You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2015/06/17 23:09:30 UTC

[30/57] [partial] struts git commit: Merges xwork packages into struts

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java b/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java
new file mode 100644
index 0000000..dc8d856
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/AnnotationValidationConfigurationBuilder.java
@@ -0,0 +1,901 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+import com.opensymphony.xwork2.validator.annotations.*;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <code>AnnotationValidationConfigurationBuilder</code>
+ *
+ * @author Rainer Hermanns
+ * @author jepjep
+ * @version $Id$
+ */
+public class AnnotationValidationConfigurationBuilder {
+
+    private static final Pattern SETTER_PATTERN = Pattern.compile("set([A-Z][A-Za-z0-9]*)$");
+    private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is|has)([A-Z][A-Za-z0-9]*)$");
+
+    private ValidatorFactory validatorFactory;
+
+    public AnnotationValidationConfigurationBuilder(ValidatorFactory fac) {
+        this.validatorFactory = fac;
+    }
+
+    private List<ValidatorConfig> processAnnotations(Object o) {
+
+        List<ValidatorConfig> result = new ArrayList<>();
+
+        String fieldName = null;
+        String methodName = null;
+
+        Annotation[] annotations = null;
+
+        if (o instanceof Class) {
+            Class clazz = (Class) o;
+            annotations = clazz.getAnnotations();
+        }
+
+        if (o instanceof Method) {
+            Method method = (Method) o;
+            fieldName = resolvePropertyName(method);
+            methodName = method.getName();
+
+            annotations = method.getAnnotations();
+        }
+
+        if (annotations != null) {
+            for (Annotation a : annotations) {
+
+                // Process collection of custom validations
+                if (a instanceof Validations) {
+                    processValidationAnnotation(a, fieldName, methodName, result);
+                }
+
+                // Process single custom validator
+                if (a instanceof Validation) {
+                    Validation v = (Validation) a;
+                    if (v.validations() != null) {
+                        for (Validations val : v.validations()) {
+                            processValidationAnnotation(val, fieldName, methodName, result);
+                        }
+                    }
+                }
+                // Process single custom validator
+                else if (a instanceof ExpressionValidator) {
+                    ExpressionValidator v = (ExpressionValidator) a;
+                    ValidatorConfig temp = processExpressionValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process single custom validator
+                else if (a instanceof CustomValidator) {
+                    CustomValidator v = (CustomValidator) a;
+                    ValidatorConfig temp = processCustomValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process ConversionErrorFieldValidator
+                else if (a instanceof ConversionErrorFieldValidator) {
+                    ConversionErrorFieldValidator v = (ConversionErrorFieldValidator) a;
+                    ValidatorConfig temp = processConversionErrorFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+
+                }
+                // Process DateRangeFieldValidator
+                else if (a instanceof DateRangeFieldValidator) {
+                    DateRangeFieldValidator v = (DateRangeFieldValidator) a;
+                    ValidatorConfig temp = processDateRangeFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+
+                }
+                // Process EmailValidator
+                else if (a instanceof EmailValidator) {
+                    EmailValidator v = (EmailValidator) a;
+                    ValidatorConfig temp = processEmailValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process FieldExpressionValidator
+                else if (a instanceof FieldExpressionValidator) {
+                    FieldExpressionValidator v = (FieldExpressionValidator) a;
+                    ValidatorConfig temp = processFieldExpressionValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process IntRangeFieldValidator
+                else if (a instanceof IntRangeFieldValidator) {
+                    IntRangeFieldValidator v = (IntRangeFieldValidator) a;
+                    ValidatorConfig temp = processIntRangeFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process ShortRangeFieldValidator
+                else if (a instanceof ShortRangeFieldValidator) {
+                    ShortRangeFieldValidator v = (ShortRangeFieldValidator) a;
+                    ValidatorConfig temp = processShortRangeFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process DoubleRangeFieldValidator
+                else if (a instanceof DoubleRangeFieldValidator) {
+                    DoubleRangeFieldValidator v = (DoubleRangeFieldValidator) a;
+                    ValidatorConfig temp = processDoubleRangeFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process RequiredFieldValidator
+                else if (a instanceof RequiredFieldValidator) {
+                    RequiredFieldValidator v = (RequiredFieldValidator) a;
+                    ValidatorConfig temp = processRequiredFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process RequiredStringValidator
+                else if (a instanceof RequiredStringValidator) {
+                    RequiredStringValidator v = (RequiredStringValidator) a;
+                    ValidatorConfig temp = processRequiredStringValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process StringLengthFieldValidator
+                else if (a instanceof StringLengthFieldValidator) {
+                    StringLengthFieldValidator v = (StringLengthFieldValidator) a;
+                    ValidatorConfig temp = processStringLengthFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process UrlValidator
+                else if (a instanceof UrlValidator) {
+                    UrlValidator v = (UrlValidator) a;
+                    ValidatorConfig temp = processUrlValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+
+                }
+                // Process ConditionalVisitorFieldValidator
+                else if (a instanceof ConditionalVisitorFieldValidator) {
+                    ConditionalVisitorFieldValidator v = (ConditionalVisitorFieldValidator) a;
+                    ValidatorConfig temp = processConditionalVisitorFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process VisitorFieldValidator
+                else if (a instanceof VisitorFieldValidator) {
+                    VisitorFieldValidator v = (VisitorFieldValidator) a;
+                    ValidatorConfig temp = processVisitorFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+                // Process RegexFieldValidator
+                else if (a instanceof RegexFieldValidator) {
+                    RegexFieldValidator v = (RegexFieldValidator) a;
+                    ValidatorConfig temp = processRegexFieldValidatorAnnotation(v, fieldName, methodName);
+                    if (temp != null) {
+                        result.add(temp);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    private void processValidationAnnotation(Annotation a, String fieldName, String methodName, List<ValidatorConfig> result) {
+        Validations validations = (Validations) a;
+        CustomValidator[] cv = validations.customValidators();
+        if (cv != null) {
+            for (CustomValidator v : cv) {
+                ValidatorConfig temp = processCustomValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        ExpressionValidator[] ev = validations.expressions();
+        if (ev != null) {
+            for (ExpressionValidator v : ev) {
+                ValidatorConfig temp = processExpressionValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        ConversionErrorFieldValidator[] cef = validations.conversionErrorFields();
+        if (cef != null) {
+            for (ConversionErrorFieldValidator v : cef) {
+                ValidatorConfig temp = processConversionErrorFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        DateRangeFieldValidator[] drfv = validations.dateRangeFields();
+        if (drfv != null) {
+            for (DateRangeFieldValidator v : drfv) {
+                ValidatorConfig temp = processDateRangeFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        EmailValidator[] emv = validations.emails();
+        if (emv != null) {
+            for (EmailValidator v : emv) {
+                ValidatorConfig temp = processEmailValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        FieldExpressionValidator[] fev = validations.fieldExpressions();
+        if (fev != null) {
+            for (FieldExpressionValidator v : fev) {
+                ValidatorConfig temp = processFieldExpressionValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        IntRangeFieldValidator[] irfv = validations.intRangeFields();
+        if (irfv != null) {
+            for (IntRangeFieldValidator v : irfv) {
+                ValidatorConfig temp = processIntRangeFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        RegexFieldValidator[] rfv = validations.regexFields();
+        if (rfv != null) {
+            for (RegexFieldValidator v : rfv) {
+                ValidatorConfig temp = processRegexFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        RequiredFieldValidator[] rv = validations.requiredFields();
+        if (rv != null) {
+            for (RequiredFieldValidator v : rv) {
+                ValidatorConfig temp = processRequiredFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        RequiredStringValidator[] rsv = validations.requiredStrings();
+        if (rsv != null) {
+            for (RequiredStringValidator v : rsv) {
+                ValidatorConfig temp = processRequiredStringValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        StringLengthFieldValidator[] slfv = validations.stringLengthFields();
+        if (slfv != null) {
+            for (StringLengthFieldValidator v : slfv) {
+                ValidatorConfig temp = processStringLengthFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        UrlValidator[] uv = validations.urls();
+        if (uv != null) {
+            for (UrlValidator v : uv) {
+                ValidatorConfig temp = processUrlValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        ConditionalVisitorFieldValidator[] cvfv = validations.conditionalVisitorFields();
+        if (cvfv != null) {
+            for (ConditionalVisitorFieldValidator v : cvfv) {
+                ValidatorConfig temp = processConditionalVisitorFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+        VisitorFieldValidator[] vfv = validations.visitorFields();
+        if (vfv != null) {
+            for (VisitorFieldValidator v : vfv) {
+                ValidatorConfig temp = processVisitorFieldValidatorAnnotation(v, fieldName, methodName);
+                if (temp != null) {
+                    result.add(temp);
+                }
+            }
+        }
+    }
+
+    private ValidatorConfig processExpressionValidatorAnnotation(ExpressionValidator v, String fieldName, String methodName) {
+        String validatorType = "expression";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        }
+
+        params.put("expression", v.expression());
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processCustomValidatorAnnotation(CustomValidator v, String fieldName, String methodName) {
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+
+        String validatorType = v.type();
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+
+        Annotation[] recursedAnnotations = v.parameters();
+
+        if (recursedAnnotations != null) {
+            for (Annotation a2 : recursedAnnotations) {
+                if (a2 instanceof ValidationParameter) {
+                    ValidationParameter parameter = (ValidationParameter) a2;
+                    String parameterName = parameter.name();
+                    String parameterValue = parameter.value();
+                    params.put(parameterName, parameterValue);
+                }
+            }
+        }
+
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processRegexFieldValidatorAnnotation(RegexFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "regex";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        params.put("regex", v.regex());
+        params.put("regexExpression", v.regexExpression());
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .addParam("trim", v.trim())
+                .addParam("trimExpression", v.trimExpression())
+                .addParam("caseSensitive", v.caseSensitive())
+                .addParam("caseSensitiveExpression", v.caseSensitiveExpression())
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processConditionalVisitorFieldValidatorAnnotation(ConditionalVisitorFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "conditionalvisitor";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        params.put("expression", v.expression());
+        params.put("context", v.context());
+        params.put("appendPrefix", String.valueOf(v.appendPrefix()));
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+
+    private ValidatorConfig processVisitorFieldValidatorAnnotation(VisitorFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "visitor";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        params.put("context", v.context());
+        params.put("appendPrefix", String.valueOf(v.appendPrefix()));
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processUrlValidatorAnnotation(UrlValidator v, String fieldName, String methodName) {
+        String validatorType = "url";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+        if (StringUtils.isNotEmpty(v.urlRegex())) {
+            params.put("urlRegex", v.urlRegex());
+        }
+        if (StringUtils.isNotEmpty(v.urlRegexExpression())) {
+            params.put("urlRegexExpression", v.urlRegexExpression());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processStringLengthFieldValidatorAnnotation(StringLengthFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "stringlength";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        if (StringUtils.isNotEmpty(v.maxLength())) {
+            params.put("maxLength", v.maxLength());
+        }
+        if (StringUtils.isNotEmpty(v.minLength())) {
+            params.put("minLength", v.minLength());
+        }
+        if (StringUtils.isNotEmpty(v.maxLengthExpression())) {
+            params.put("maxLengthExpression", v.maxLengthExpression());
+        }
+        if (StringUtils.isNotEmpty(v.minLengthExpression())) {
+            params.put("minLengthExpression", v.minLengthExpression());
+        }
+        if (StringUtils.isNotEmpty(v.trimExpression())){
+            params.put("trimExpression", v.trimExpression());
+        } else {
+            params.put("trim", String.valueOf(v.trim()));
+        }
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private Date parseDateString(String value, String format) {
+
+        SimpleDateFormat d0 = null;
+        if (StringUtils.isNotEmpty(format)) {
+            d0 = new SimpleDateFormat(format);
+        }
+        SimpleDateFormat d1 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, Locale.getDefault());
+        SimpleDateFormat d2 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, Locale.getDefault());
+        SimpleDateFormat d3 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
+        SimpleDateFormat[] dfs = (d0 != null ? new SimpleDateFormat[]{d0, d1, d2, d3} : new SimpleDateFormat[]{d1, d2, d3});
+        for (SimpleDateFormat df : dfs)
+            try {
+                Date check = df.parse(value);
+                if (check != null) {
+                    return check;
+                }
+            } catch (ParseException ignore) {
+            }
+        return null;
+    }
+
+    private ValidatorConfig processRequiredStringValidatorAnnotation(RequiredStringValidator v, String fieldName, String methodName) {
+        String validatorType = "requiredstring";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        params.put("trim", String.valueOf(v.trim()));
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageParams(v.messageParams())
+                .messageKey(v.key())
+                .build();
+    }
+
+    private ValidatorConfig processRequiredFieldValidatorAnnotation(RequiredFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "required";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processIntRangeFieldValidatorAnnotation(IntRangeFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "int";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        if (v.min() != null && v.min().length() > 0) {
+            params.put("min", v.min());
+        }
+        if (v.max() != null && v.max().length() > 0) {
+            params.put("max", v.max());
+        }
+        if (StringUtils.isNotEmpty(v.maxExpression())) {
+            params.put("maxExpression", v.maxExpression());
+        }
+        if (StringUtils.isNotEmpty(v.minExpression())) {
+            params.put("minExpression", v.minExpression());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processShortRangeFieldValidatorAnnotation(ShortRangeFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "short";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        if (StringUtils.isNotEmpty(v.min())) {
+            params.put("min", v.min());
+        }
+        if (StringUtils.isNotEmpty(v.max())) {
+            params.put("max", v.max());
+        }
+        if (StringUtils.isNotEmpty(v.maxExpression())) {
+            params.put("maxExpression", v.maxExpression());
+        }
+        if (StringUtils.isNotEmpty(v.minExpression())) {
+            params.put("minExpression", v.minExpression());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processDoubleRangeFieldValidatorAnnotation(DoubleRangeFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "double";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (v.fieldName() != null && v.fieldName().length() > 0) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        if (v.minInclusive() != null && v.minInclusive().length() > 0) {
+            params.put("minInclusive", v.minInclusive());
+        }
+        if (v.maxInclusive() != null && v.maxInclusive().length() > 0) {
+            params.put("maxInclusive", v.maxInclusive());
+        }
+
+        if (v.minExclusive() != null && v.minExclusive().length() > 0) {
+            params.put("minExclusive", v.minExclusive());
+        }
+        if (v.maxExclusive() != null && v.maxExclusive().length() > 0) {
+            params.put("maxExclusive", v.maxExclusive());
+        }
+
+        if (StringUtils.isNotEmpty(v.minInclusiveExpression())) {
+            params.put("minInclusiveExpression", v.minInclusiveExpression());
+        }
+        if (StringUtils.isNotEmpty(v.maxInclusiveExpression())) {
+            params.put("maxInclusiveExpression", v.maxInclusiveExpression());
+        }
+
+        if (StringUtils.isNotEmpty(v.minExclusiveExpression())) {
+            params.put("minExclusiveExpression", v.minExclusiveExpression());
+        }
+        if (StringUtils.isNotEmpty(v.maxExclusiveExpression())) {
+            params.put("maxExclusiveExpression", v.maxExclusiveExpression());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processFieldExpressionValidatorAnnotation(FieldExpressionValidator v, String fieldName, String methodName) {
+        String validatorType = "fieldexpression";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        params.put("expression", v.expression());
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processEmailValidatorAnnotation(EmailValidator v, String fieldName, String methodName) {
+        String validatorType = "email";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processDateRangeFieldValidatorAnnotation(DateRangeFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "date";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (v.fieldName() != null && v.fieldName().length() > 0) {
+            params.put("fieldName", v.fieldName());
+        }
+        if (v.min() != null && v.min().length() > 0) {
+            final Date minDate = parseDateString(v.min(), v.dateFormat());
+            params.put("min", minDate == null ? v.min() : minDate);
+        }
+        if (v.max() != null && v.max().length() > 0) {
+            final Date maxDate = parseDateString(v.max(), v.dateFormat());
+            params.put("max", maxDate == null ? v.max() : maxDate);
+        }
+
+        if (StringUtils.isNotEmpty(v.minExpression())) {
+            params.put("minExpression", v.minExpression());
+        }
+        if (StringUtils.isNotEmpty(v.maxExpression())) {
+            params.put("maxExpression", v.maxExpression());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    private ValidatorConfig processConversionErrorFieldValidatorAnnotation(ConversionErrorFieldValidator v, String fieldName, String methodName) {
+        String validatorType = "conversion";
+
+        Map<String, Object> params = new HashMap<>();
+
+        if (fieldName != null) {
+            params.put("fieldName", fieldName);
+        } else if (StringUtils.isNotEmpty(v.fieldName())) {
+            params.put("fieldName", v.fieldName());
+        }
+
+        validatorFactory.lookupRegisteredValidatorType(validatorType);
+        return new ValidatorConfig.Builder(validatorType)
+                .addParams(params)
+                .addParam("methodName", methodName)
+                .addParam("repopulateField", v.repopulateField())
+                .shortCircuit(v.shortCircuit())
+                .defaultMessage(v.message())
+                .messageKey(v.key())
+                .messageParams(v.messageParams())
+                .build();
+    }
+
+    public List<ValidatorConfig> buildAnnotationClassValidatorConfigs(Class aClass) {
+
+        List<ValidatorConfig> result = new ArrayList<>();
+
+        List<ValidatorConfig> temp = processAnnotations(aClass);
+        if (temp != null) {
+            result.addAll(temp);
+        }
+
+        Method[] methods = aClass.getDeclaredMethods();
+
+        if (methods != null) {
+            for (Method method : methods) {
+                temp = processAnnotations(method);
+                if (temp != null) {
+                    result.addAll(temp);
+                }
+            }
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Returns the property name for a method.
+     * This method is independant from property fields.
+     *
+     * @param method The method to get the property name for.
+     * @return the property name for given method; null if non could be resolved.
+     */
+    public String resolvePropertyName(Method method) {
+
+        Matcher matcher = SETTER_PATTERN.matcher(method.getName());
+        if (matcher.matches() && method.getParameterTypes().length == 1) {
+            String raw = matcher.group(1);
+            return raw.substring(0, 1).toLowerCase() + raw.substring(1);
+        }
+
+        matcher = GETTER_PATTERN.matcher(method.getName());
+        if (matcher.matches() && method.getParameterTypes().length == 0) {
+            String raw = matcher.group(2);
+            return raw.substring(0, 1).toLowerCase() + raw.substring(1);
+        }
+
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java b/core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java
new file mode 100644
index 0000000..d9d7401
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/DefaultActionValidatorManager.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.FileManager;
+import com.opensymphony.xwork2.FileManagerFactory;
+import com.opensymphony.xwork2.XWorkConstants;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+
+
+/**
+ * This is the entry point into XWork's rule-based validation framework.
+ * <p/>
+ * Validation rules are specified in XML configuration files named <code>className-contextName-validation.xml</code> where
+ * className is the name of the class the configuration is for and -contextName is optional
+ * (contextName is an arbitrary key that is used to look up additional validation rules for a
+ * specific context).
+ *
+ * @author Jason Carreira
+ * @author Mark Woon
+ * @author James House
+ * @author Rainer Hermanns
+ */
+public class DefaultActionValidatorManager implements ActionValidatorManager {
+
+    /** The file suffix for any validation file. */
+    protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
+
+    private final Map<String, List<ValidatorConfig>> validatorCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
+    private final Map<String, List<ValidatorConfig>> validatorFileCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
+    private final Logger LOG = LogManager.getLogger(DefaultActionValidatorManager.class);
+    private ValidatorFactory validatorFactory;
+    private ValidatorFileParser validatorFileParser;
+    private FileManager fileManager;
+    private boolean reloadingConfigs;
+
+    @Inject
+    public void setValidatorFileParser(ValidatorFileParser parser) {
+        this.validatorFileParser = parser;
+    }
+    
+    @Inject
+    public void setValidatorFactory(ValidatorFactory fac) {
+        this.validatorFactory = fac;
+    }
+
+    @Inject
+    public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
+        this.fileManager = fileManagerFactory.getFileManager();
+    }
+
+    @Inject(value = XWorkConstants.RELOAD_XML_CONFIGURATION, required = false)
+    public void setReloadingConfigs(String reloadingConfigs) {
+        this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
+    }
+
+    public synchronized List<Validator> getValidators(Class clazz, String context) {
+        return getValidators(clazz, context, null);
+    }
+
+    public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
+        final String validatorKey = buildValidatorKey(clazz, context);
+
+        if (validatorCache.containsKey(validatorKey)) {
+            if (reloadingConfigs) {
+                validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
+            }
+        } else {
+            validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
+        }
+        ValueStack stack = ActionContext.getContext().getValueStack();
+
+        // get the set of validator configs
+        List<ValidatorConfig> cfgs = validatorCache.get(validatorKey);
+
+        // create clean instances of the validators for the caller's use
+        ArrayList<Validator> validators = new ArrayList<>(cfgs.size());
+        for (ValidatorConfig cfg : cfgs) {
+            if (method == null || method.equals(cfg.getParams().get("methodName"))) {
+                Validator validator = validatorFactory.getValidator(cfg);
+                validator.setValidatorType(cfg.getType());
+                validator.setValueStack(stack);
+                validators.add(validator);
+            }
+        }
+        return validators;
+    }
+
+    public void validate(Object object, String context) throws ValidationException {
+        validate(object, context, (String) null);
+    }
+
+    public void validate(Object object, String context, String method) throws ValidationException {
+        ValidatorContext validatorContext = new DelegatingValidatorContext(object);
+        validate(object, context, validatorContext, method);
+    }
+
+    public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
+        validate(object, context, validatorContext, null);
+    }
+
+    public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
+        List<Validator> validators = getValidators(object.getClass(), context, method);
+        Set<String> shortcircuitedFields = null;
+
+        for (final Validator validator : validators) {
+        try {
+                validator.setValidatorContext(validatorContext);
+
+            LOG.debug("Running validator: {} for object {} and method {}", validator, object, method);
+
+                FieldValidator fValidator = null;
+                String fullFieldName = null;
+
+                if (validator instanceof FieldValidator) {
+                    fValidator = (FieldValidator) validator;
+                    fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName());
+
+                    if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
+                        LOG.debug("Short-circuited, skipping");
+                        continue;
+                    }
+                }
+
+                if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
+                    // get number of existing errors
+                    List<String> errs = null;
+
+                    if (fValidator != null) {
+                        if (validatorContext.hasFieldErrors()) {
+                            Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
+
+                            if (fieldErrors != null) {
+                                errs = new ArrayList<>(fieldErrors);
+                            }
+                        }
+                    } else if (validatorContext.hasActionErrors()) {
+                        Collection<String> actionErrors = validatorContext.getActionErrors();
+
+                        if (actionErrors != null) {
+                            errs = new ArrayList<String>(actionErrors);
+                        }
+                    }
+
+                    validator.validate(object);
+
+                    if (fValidator != null) {
+                        if (validatorContext.hasFieldErrors()) {
+                            Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
+
+                            if ((errCol != null) && !errCol.equals(errs)) {
+                                LOG.debug("Short-circuiting on field validation");
+
+                                if (shortcircuitedFields == null) {
+                                    shortcircuitedFields = new TreeSet<>();
+                                }
+
+                                shortcircuitedFields.add(fullFieldName);
+                            }
+                        }
+                    } else if (validatorContext.hasActionErrors()) {
+                        Collection<String> errCol = validatorContext.getActionErrors();
+
+                        if ((errCol != null) && !errCol.equals(errs)) {
+                            LOG.debug("Short-circuiting");
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                validator.validate(object);
+            }
+            finally {
+                validator.setValidatorContext(null);
+            }
+        }
+    }
+
+    /**
+     * Builds a key for validators - used when caching validators.
+     *
+     * @param clazz the action.
+     * @param context the action's context.
+     * @return a validator key which is the class name plus context.
+     */
+    protected static String buildValidatorKey(Class clazz, String context) {
+        StringBuilder sb = new StringBuilder(clazz.getName());
+        sb.append("/");
+        sb.append(context);
+        return sb.toString();
+    }
+
+    private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
+        String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
+
+        return loadFile(fileName, aClass, checkFile);
+    }
+
+    private List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
+        String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
+
+        return loadFile(fileName, aClass, checkFile);
+    }
+
+    /**
+     * <p>This method 'collects' all the validator configurations for a given
+     * action invocation.</p>
+     *
+     * <p>It will traverse up the class hierarchy looking for validators for every super class
+     * and directly implemented interface of the current action, as well as adding validators for
+     * any alias of this invocation. Nifty!</p>
+     *
+     * <p>Given the following class structure:
+     * <pre>
+     *   interface Thing;
+     *   interface Animal extends Thing;
+     *   interface Quadraped extends Animal;
+     *   class AnimalImpl implements Animal;
+     *   class QuadrapedImpl extends AnimalImpl implements Quadraped;
+     *   class Dog extends QuadrapedImpl;
+     * </pre></p>
+     *
+     * <p>This method will look for the following config files for Dog:
+     * <pre>
+     *   Animal
+     *   Animal-context
+     *   AnimalImpl
+     *   AnimalImpl-context
+     *   Quadraped
+     *   Quadraped-context
+     *   QuadrapedImpl
+     *   QuadrapedImpl-context
+     *   Dog
+     *   Dog-context
+     * </pre></p>
+     *
+     * <p>Note that the validation rules for Thing is never looked for because no class in the
+     * hierarchy directly implements Thing.</p>
+     *
+     * @param clazz the Class to look up validators for.
+     * @param context the context to use when looking up validators.
+     * @param checkFile true if the validation config file should be checked to see if it has been
+     *      updated.
+     * @param checked the set of previously checked class-contexts, null if none have been checked
+     * @return a list of validator configs for the given class and context.
+     */
+    private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
+        List<ValidatorConfig> validatorConfigs = new ArrayList<>();
+
+        if (checked == null) {
+            checked = new TreeSet<String>();
+        } else if (checked.contains(clazz.getName())) {
+            return validatorConfigs;
+        }
+
+        if (clazz.isInterface()) {
+            for (Class anInterface : clazz.getInterfaces()) {
+                validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
+             }
+        } else {
+            if (!clazz.equals(Object.class)) {
+                validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
+            }
+        }
+
+        // look for validators for implemented interfaces
+        for (Class anInterface1 : clazz.getInterfaces()) {
+            if (checked.contains(anInterface1.getName())) {
+                continue;
+            }
+
+            validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
+
+            if (context != null) {
+                validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
+            }
+
+            checked.add(anInterface1.getName());
+        }
+
+        validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
+
+        if (context != null) {
+            validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
+        }
+
+        checked.add(clazz.getName());
+
+        return validatorConfigs;
+    }
+
+    private List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
+        List<ValidatorConfig> retList = Collections.emptyList();
+        URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
+        if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) {
+            try (InputStream is = fileManager.loadFile(fileUrl)) {
+                if (is != null) {
+                    retList = new ArrayList<>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
+                }
+            } catch (IOException e) {
+                LOG.error("Caught exception while loading file {}", fileName, e);
+            }
+
+            validatorFileCache.put(fileName, retList);
+        } else {
+            retList = validatorFileCache.get(fileName);
+        }
+
+        return retList;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java b/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java
new file mode 100644
index 0000000..d4d7ff7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+
+/**
+ * Default validator factory
+ *
+ * @version $Date$ $Id$
+ * @author Jason Carreira
+ * @author James House
+ */
+public class DefaultValidatorFactory implements ValidatorFactory {
+
+    protected Map<String, String> validators = new HashMap<>();
+    private static Logger LOG = LogManager.getLogger(DefaultValidatorFactory.class);
+    protected ObjectFactory objectFactory;
+    protected ValidatorFileParser validatorFileParser;
+
+    @Inject
+    public DefaultValidatorFactory(@Inject ObjectFactory objectFactory, @Inject ValidatorFileParser parser) {
+        this.objectFactory = objectFactory;
+        this.validatorFileParser = parser;
+        parseValidators();
+    }
+
+    public Validator getValidator(ValidatorConfig cfg) {
+
+        String className = lookupRegisteredValidatorType(cfg.getType());
+
+        Validator validator;
+
+        try {
+            // instantiate the validator, and set configured parameters
+            //todo - can this use the ThreadLocal?
+            validator = objectFactory.buildValidator(className, cfg.getParams(), ActionContext.getContext().getContextMap());
+        } catch (Exception e) {
+            final String msg = "There was a problem creating a Validator of type " + className + " : caused by " + e.getMessage();
+            throw new XWorkException(msg, e, cfg);
+        }
+
+        // set other configured properties
+        validator.setMessageKey(cfg.getMessageKey());
+        validator.setDefaultMessage(cfg.getDefaultMessage());
+        validator.setMessageParameters(cfg.getMessageParams());
+        if (validator instanceof ShortCircuitableValidator) {
+            ((ShortCircuitableValidator) validator).setShortCircuit(cfg.isShortCircuit());
+        }
+
+        return validator;
+    }
+
+    public void registerValidator(String name, String className) {
+        LOG.debug("Registering validator of class {} with name {}", className, name);
+        validators.put(name, className);
+    }
+
+    public String lookupRegisteredValidatorType(String name) {
+        // lookup the validator class mapped to the type name
+        String className = validators.get(name);
+
+        if (className == null) {
+            throw new IllegalArgumentException("There is no validator class mapped to the name " + name);
+        }
+
+        return className;
+    }
+
+    private void parseValidators() {
+        LOG.debug("Loading validator definitions.");
+
+        List<File> files = new ArrayList<>();
+        try {
+            // Get custom validator configurations via the classpath
+            Iterator<URL> urls = ClassLoaderUtil.getResources("", DefaultValidatorFactory.class, false);
+            while (urls.hasNext()) {
+                URL u = urls.next();
+                try {
+                    URI uri = new URI(u.toExternalForm().replaceAll(" ", "%20"));
+                    if (!uri.isOpaque() && "file".equalsIgnoreCase(uri.getScheme())) {
+                        File f = new File(uri);
+                        FilenameFilter filter = new FilenameFilter() {
+                            public boolean accept(File file, String fileName) {
+                                return fileName.contains("-validators.xml");
+                            }
+                        };
+                        // First check if this is a directory
+                        // If yes, then just do a "list" to get all files in this directory
+                        // and match the filenames with *-validators.xml. If the filename
+                        // matches then add to the list of files to be parsed
+                        if (f.isDirectory()) {
+                            try {
+                                File[] ff = f.listFiles(filter);
+                                if ( ff != null && ff.length > 0) {
+                                    files.addAll(Arrays.asList(ff));
+                                }
+                            } catch (SecurityException se) {
+                                LOG.error("Security Exception while accessing directory '{}'", f, se);
+                            }
+
+                        } else {
+                            // If this is not a directory, then get hold of the inputstream.
+                            // If its not a ZipInputStream, then create a ZipInputStream out
+                            // of it. The intention is to allow nested jar files to be scanned
+                            // for *-validators.xml.
+                            // Ex: struts-app.jar -> MyApp.jar -> Login-validators.xml should be
+                            // parsed and loaded.
+                            ZipInputStream zipInputStream = null;
+                            try (InputStream inputStream = u.openStream()) {
+                                if (inputStream instanceof ZipInputStream) {
+                                    zipInputStream = (ZipInputStream) inputStream;
+                                } else {
+                                    zipInputStream = new ZipInputStream(inputStream);
+                                }
+                                ZipEntry zipEntry = zipInputStream.getNextEntry();
+                                while (zipEntry != null) {
+                                    if (zipEntry.getName().endsWith("-validators.xml")) {
+                                        LOG.trace("Adding validator {}", zipEntry.getName());
+                                        files.add(new File(zipEntry.getName()));
+                                    }
+                                    zipEntry = zipInputStream.getNextEntry();
+                                }
+                            } finally {
+                                //cleanup
+                                if (zipInputStream != null) {
+                                    zipInputStream.close();
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception ex) {
+                    LOG.error("Unable to load {}", u, ex);
+                }
+            }
+        } catch (IOException e) {
+            throw new ConfigurationException("Unable to parse validators", e);
+        }
+
+        // Parse default validator configurations
+        String resourceName = "com/opensymphony/xwork2/validator/validators/default.xml";
+        retrieveValidatorConfiguration(resourceName);
+
+        // Overwrite and extend defaults with application specific validator configurations
+        resourceName = "validators.xml";
+        retrieveValidatorConfiguration(resourceName);
+
+        // Add custom (plugin) specific validator configurations
+        for (File file : files) {
+            retrieveValidatorConfiguration(file.getName());
+        }
+    }
+
+    private void retrieveValidatorConfiguration(String resourceName) {
+        InputStream is = ClassLoaderUtil.getResourceAsStream(resourceName, DefaultValidatorFactory.class);
+        if (is != null) {
+            validatorFileParser.parseValidatorDefinitions(validators, is, resourceName);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java b/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java
new file mode 100644
index 0000000..b640996
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.providers.XmlHelper;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.DomHelper;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.w3c.dom.*;
+import org.xml.sax.InputSource;
+
+import java.io.InputStream;
+import java.util.*;
+
+
+/**
+ * Parse the validation file. (eg. MyAction-validation.xml, MyAction-actionAlias-validation.xml)
+ * to return a List of ValidatorConfig encapsulating the validator information.
+ *
+ * @author Jason Carreira
+ * @author James House
+ * @author tm_jee ( tm_jee (at) yahoo.co.uk )
+ * @author Rob Harrop
+ * @author Rene Gielen
+ * @author Martin Gilday
+ * 
+ * @see com.opensymphony.xwork2.validator.ValidatorConfig
+ */
+public class DefaultValidatorFileParser implements ValidatorFileParser {
+
+    private static Logger LOG = LogManager.getLogger(DefaultValidatorFileParser.class);
+
+    static final String DEFAULT_MULTI_TEXTVALUE_SEPARATOR = " ";
+    static final String MULTI_TEXTVALUE_SEPARATOR_CONFIG_KEY = "xwork.validatorfileparser.multi_textvalue_separator";
+
+    private ObjectFactory objectFactory;
+    private String multiTextvalueSeparator=DEFAULT_MULTI_TEXTVALUE_SEPARATOR;
+
+    @Inject(value=MULTI_TEXTVALUE_SEPARATOR_CONFIG_KEY, required = false)
+    public void setMultiTextvalueSeparator(String type) {
+        multiTextvalueSeparator = type;
+    }
+
+    public String getMultiTextvalueSeparator() {
+        return multiTextvalueSeparator;
+    }
+
+    @Inject
+    public void setObjectFactory(ObjectFactory fac) {
+        this.objectFactory = fac;
+    }
+
+    public List<ValidatorConfig> parseActionValidatorConfigs(ValidatorFactory validatorFactory, InputStream is, final String resourceName) {
+        List<ValidatorConfig> validatorCfgs = new ArrayList<ValidatorConfig>();
+
+        InputSource in = new InputSource(is);
+        in.setSystemId(resourceName);
+
+        Map<String, String> dtdMappings = new HashMap<String, String>();
+        dtdMappings.put("-//Apache Struts//XWork Validator 1.0//EN", "xwork-validator-1.0.dtd");
+        dtdMappings.put("-//Apache Struts//XWork Validator 1.0.2//EN", "xwork-validator-1.0.2.dtd");
+        dtdMappings.put("-//Apache Struts//XWork Validator 1.0.3//EN", "xwork-validator-1.0.3.dtd");
+        dtdMappings.put("-//Apache Struts//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd");
+
+        Document doc = DomHelper.parse(in, dtdMappings);
+
+        if (doc != null) {
+            NodeList fieldNodes = doc.getElementsByTagName("field");
+
+            // BUG: xw-305: Let validator be parsed first and hence added to 
+            // the beginning of list and therefore evaluated first, so short-circuting
+            // it will not cause field-level validator to be kicked off.
+            {
+                NodeList validatorNodes = doc.getElementsByTagName("validator");
+                addValidatorConfigs(validatorFactory, validatorNodes, new HashMap<String, Object>(), validatorCfgs);
+            }
+
+            for (int i = 0; i < fieldNodes.getLength(); i++) {
+                Element fieldElement = (Element) fieldNodes.item(i);
+                String fieldName = fieldElement.getAttribute("name");
+                Map<String, Object> extraParams = new HashMap<String, Object>();
+                extraParams.put("fieldName", fieldName);
+
+                NodeList validatorNodes = fieldElement.getElementsByTagName("field-validator");
+                addValidatorConfigs(validatorFactory, validatorNodes, extraParams, validatorCfgs);
+            }
+        }
+
+        return validatorCfgs;
+    }
+
+
+    public void parseValidatorDefinitions(Map<String, String> validators, InputStream is, String resourceName) {
+
+        InputSource in = new InputSource(is);
+        in.setSystemId(resourceName);
+
+        Map<String, String> dtdMappings = new HashMap<String, String>();
+        dtdMappings.put("-//Apache Struts//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd");
+        dtdMappings.put("-//Apache Struts//XWork Validator Definition 1.0//EN", "xwork-validator-definition-1.0.dtd");
+
+        Document doc = DomHelper.parse(in, dtdMappings);
+
+        if (doc != null) {
+            NodeList nodes = doc.getElementsByTagName("validator");
+
+            for (int i = 0; i < nodes.getLength(); i++) {
+                Element validatorElement = (Element) nodes.item(i);
+                String name = validatorElement.getAttribute("name");
+                String className = validatorElement.getAttribute("class");
+
+                try {
+                    // catch any problems here
+                    objectFactory.buildValidator(className, new HashMap<String, Object>(), ActionContext.getContext().getContextMap());
+                    validators.put(name, className);
+                } catch (Exception e) {
+                    throw new ConfigurationException("Unable to load validator class " + className, e, validatorElement);
+                }
+            }
+        }
+    }
+
+    /**
+     * Extract trimmed text value from the given DOM element, ignoring XML comments. Appends all CharacterData nodes
+     * and EntityReference nodes into a single String value, excluding Comment nodes.
+     * This method is based on a method originally found in DomUtils class of Springframework.
+     *
+     * @see org.w3c.dom.CharacterData
+     * @see org.w3c.dom.EntityReference
+     * @see org.w3c.dom.Comment
+     */
+    public String getTextValue(Element valueEle) {
+        StringBuilder value = new StringBuilder();
+        NodeList nl = valueEle.getChildNodes();
+        boolean firstCDataFound = false;
+        for (int i = 0; i < nl.getLength(); i++) {
+            Node item = nl.item(i);
+            if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
+                final String nodeValue = item.getNodeValue();
+                if (nodeValue != null) {
+                    if (firstCDataFound) {
+                        value.append(getMultiTextvalueSeparator());
+                    } else {
+                        firstCDataFound = true;
+                    }
+                    value.append(nodeValue.trim());
+                }
+            }
+        }
+        return value.toString().trim();
+    }
+
+    private void addValidatorConfigs(ValidatorFactory factory, NodeList validatorNodes, Map<String, Object> extraParams, List<ValidatorConfig> validatorCfgs) {
+        for (int j = 0; j < validatorNodes.getLength(); j++) {
+            Element validatorElement = (Element) validatorNodes.item(j);
+            String validatorType = validatorElement.getAttribute("type");
+            Map<String, Object> params = new HashMap<String, Object>(extraParams);
+
+            params.putAll(XmlHelper.getParams(validatorElement));
+
+            // ensure that the type is valid...
+            try {
+                factory.lookupRegisteredValidatorType(validatorType);
+            } catch (IllegalArgumentException ex) {
+                throw new ConfigurationException("Invalid validation type: " + validatorType, validatorElement);
+            }
+
+            ValidatorConfig.Builder vCfg = new ValidatorConfig.Builder(validatorType)
+                    .addParams(params)
+                    .location(DomHelper.getLocationObject(validatorElement))
+                    .shortCircuit(Boolean.valueOf(validatorElement.getAttribute("short-circuit")).booleanValue());
+
+            NodeList messageNodes = validatorElement.getElementsByTagName("message");
+            Element messageElement = (Element) messageNodes.item(0);
+
+            final Node defaultMessageNode = messageElement.getFirstChild();
+            String defaultMessage = (defaultMessageNode == null) ? "" : defaultMessageNode.getNodeValue();
+            vCfg.defaultMessage(defaultMessage);
+
+            Map<String, String> messageParams = XmlHelper.getParams(messageElement);
+            String key = messageElement.getAttribute("key");
+
+
+            if ((key != null) && (key.trim().length() > 0)) {
+                vCfg.messageKey(key);
+
+                // Get the default message when pattern 2 is used. We are only interested in the
+                // i18n message parameters when an i18n message key is specified.
+                // pattern 1:
+                // <message key="someKey">Default message</message>
+                // pattern 2:
+                // <message key="someKey">
+                //     <param name="1">'param1'</param>
+                //     <param name="2">'param2'</param>
+                //     <param name="defaultMessage>The Default Message</param>
+                // </message>
+
+                if (messageParams.containsKey("defaultMessage")) {
+                    vCfg.defaultMessage(messageParams.get("defaultMessage").toString());
+                }
+
+                // Sort the message param. those with keys as '1', '2', '3' etc. (numeric values)
+                // are i18n message parameter, others are excluded.
+                TreeMap<Integer, String> sortedMessageParameters = new TreeMap<Integer, String>();
+                for (Map.Entry<String, String> messageParamEntry : messageParams.entrySet()) {
+
+                    try {
+                        int _order = Integer.parseInt(messageParamEntry.getKey());
+                        sortedMessageParameters.put(Integer.valueOf(_order), messageParamEntry.getValue().toString());
+                    }
+                    catch (NumberFormatException e) {
+                        // ignore if its not numeric.
+                    }
+                }
+                vCfg.messageParams(sortedMessageParameters.values().toArray(new String[sortedMessageParameters.values().size()]));
+            } else {
+                if (messageParams != null && (messageParams.size() > 0)) {
+                    // we are i18n message parameters defined but no i18n message,
+                    // let's warn the user.
+                    if (LOG.isWarnEnabled()) {
+                	LOG.warn("validator of type ["+validatorType+"] have i18n message parameters defined but no i18n message key, it's parameters will be ignored");
+                    }
+                }
+            }
+
+            validatorCfgs.add(vCfg.build());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java b/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java
new file mode 100644
index 0000000..5b11921
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+
+
+/**
+ * A default implementation of the {@link ValidatorContext} interface.
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ */
+public class DelegatingValidatorContext implements ValidatorContext {
+
+    private LocaleProvider localeProvider;
+    private TextProvider textProvider;
+    private ValidationAware validationAware;
+
+    /**
+     * Creates a new validation context given a ValidationAware object, and a text and locale provider. These objects
+     * are used internally to set errors and get and set error text.
+     */
+    public DelegatingValidatorContext(ValidationAware validationAware, TextProvider textProvider,
+                                      LocaleProvider localeProvider) {
+        this.textProvider = textProvider;
+        this.validationAware = validationAware;
+        this.localeProvider = localeProvider;
+    }
+
+    /**
+     * Creates a new validation context given an object - usually an Action. The internal objects
+     * (validation aware instance and a locale and text provider) are created based on the given action.
+     *
+     * @param object the object to use for validation (usually an Action).
+     */
+    public DelegatingValidatorContext(Object object) {
+        this.localeProvider = makeLocaleProvider(object);
+        this.validationAware = makeValidationAware(object);
+        this.textProvider = makeTextProvider(object, localeProvider);
+    }
+
+    /**
+     * Create a new validation context given a Class definition. The locale provider, text provider and
+     * the validation context are created based on the class.
+     *
+     * @param clazz the class to initialize the context with.
+     */
+    public DelegatingValidatorContext(Class clazz) {
+        localeProvider = new ActionContextLocaleProvider();
+        textProvider = new TextProviderFactory().createInstance(clazz, localeProvider);
+        validationAware = new LoggingValidationAware(clazz);
+    }
+
+    public void setActionErrors(Collection<String> errorMessages) {
+        validationAware.setActionErrors(errorMessages);
+    }
+
+    public Collection<String> getActionErrors() {
+        return validationAware.getActionErrors();
+    }
+
+    public void setActionMessages(Collection<String> messages) {
+        validationAware.setActionMessages(messages);
+    }
+
+    public Collection<String> getActionMessages() {
+        return validationAware.getActionMessages();
+    }
+
+    public void setFieldErrors(Map<String, List<String>> errorMap) {
+        validationAware.setFieldErrors(errorMap);
+    }
+
+    public Map<String, List<String>> getFieldErrors() {
+        return validationAware.getFieldErrors();
+    }
+
+    public String getFullFieldName(String fieldName) {
+        return fieldName;
+    }
+
+    public Locale getLocale() {
+        return localeProvider.getLocale();
+    }
+
+    public boolean hasKey(String key) {
+    	return textProvider.hasKey(key);
+    }
+    
+    public String getText(String aTextName) {
+        return textProvider.getText(aTextName);
+    }
+
+    public String getText(String aTextName, String defaultValue) {
+        return textProvider.getText(aTextName, defaultValue);
+    }
+
+    public String getText(String aTextName, String defaultValue, String obj) {
+        return textProvider.getText(aTextName, defaultValue, obj);
+    }
+
+    public String getText(String aTextName, List<?> args) {
+        return textProvider.getText(aTextName, args);
+    }
+
+    public String getText(String key, String[] args) {
+        return textProvider.getText(key, args);
+    }
+
+    public String getText(String aTextName, String defaultValue, List<?> args) {
+        return textProvider.getText(aTextName, defaultValue, args);
+    }
+
+    public String getText(String key, String defaultValue, String[] args) {
+        return textProvider.getText(key, defaultValue, args);
+    }
+
+    public ResourceBundle getTexts(String aBundleName) {
+        return textProvider.getTexts(aBundleName);
+    }
+
+    public String getText(String key, String defaultValue, List<?> args, ValueStack stack) {
+        return textProvider.getText(key, defaultValue, args, stack);
+    }
+
+    public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+        return textProvider.getText(key, defaultValue, args, stack);
+    }
+
+    public ResourceBundle getTexts() {
+        return textProvider.getTexts();
+    }
+
+    public void addActionError(String anErrorMessage) {
+        validationAware.addActionError(anErrorMessage);
+    }
+
+    public void addActionMessage(String aMessage) {
+        validationAware.addActionMessage(aMessage);
+    }
+
+    public void addFieldError(String fieldName, String errorMessage) {
+        validationAware.addFieldError(fieldName, errorMessage);
+    }
+
+    public boolean hasActionErrors() {
+        return validationAware.hasActionErrors();
+    }
+
+    public boolean hasActionMessages() {
+        return validationAware.hasActionMessages();
+    }
+
+    public boolean hasErrors() {
+        return validationAware.hasErrors();
+    }
+
+    public boolean hasFieldErrors() {
+        return validationAware.hasFieldErrors();
+    }
+
+    public static TextProvider makeTextProvider(Object object, LocaleProvider localeProvider) {
+        // the object argument passed through here will most probably be an ActionSupport decendant which does
+        // implements TextProvider.
+        if (object != null && object instanceof DelegatingValidatorContext) {
+            return ((DelegatingValidatorContext) object).getTextProvider();
+        } else if (localeProvider != null && localeProvider instanceof DelegatingValidatorContext) {
+            return ((DelegatingValidatorContext) localeProvider).getTextProvider();
+        }
+
+        if ((object != null) && (object instanceof TextProvider)) {
+            if (object instanceof CompositeTextProvider) {
+                return (CompositeTextProvider) object;
+            }
+            return new CompositeTextProvider(new TextProvider[]{
+                    ((TextProvider) object),
+                    new TextProviderSupport(object.getClass(), localeProvider)
+            });
+        } else if (localeProvider != null && localeProvider instanceof TextProvider) {
+            if (localeProvider instanceof CompositeTextProvider) {
+                return (CompositeTextProvider) localeProvider;
+            }
+            return new CompositeTextProvider(new TextProvider[]{
+                    ((TextProvider) localeProvider),
+                    new TextProviderSupport(localeProvider.getClass(), localeProvider)
+            });
+        } else {
+            return new TextProviderFactory().createInstance(
+                    object != null ? object.getClass() : DelegatingValidatorContext.class,
+                    localeProvider);
+        }
+    }
+
+    protected static LocaleProvider makeLocaleProvider(Object object) {
+        if (object instanceof LocaleProvider) {
+            return (LocaleProvider) object;
+        } else {
+            return new ActionContextLocaleProvider();
+        }
+    }
+
+    protected static ValidationAware makeValidationAware(Object object) {
+        if (object instanceof ValidationAware) {
+            return (ValidationAware) object;
+        } else {
+            return new LoggingValidationAware(object);
+        }
+    }
+
+    protected void setTextProvider(TextProvider textProvider) {
+        this.textProvider = textProvider;
+    }
+
+    protected TextProvider getTextProvider() {
+        return textProvider;
+    }
+
+    protected void setValidationAware(ValidationAware validationAware) {
+        this.validationAware = validationAware;
+    }
+
+    protected ValidationAware getValidationAware() {
+        return validationAware;
+    }
+
+    /**
+     * An implementation of LocaleProvider which gets the locale from the action context.
+     */
+    private static class ActionContextLocaleProvider implements LocaleProvider {
+        public Locale getLocale() {
+            return ActionContext.getContext().getLocale();
+        }
+    }
+
+    /**
+     * An implementation of ValidationAware which logs errors and messages.
+     */
+    private static class LoggingValidationAware implements ValidationAware {
+
+        private Logger log;
+
+        public LoggingValidationAware(Class clazz) {
+            log = LogManager.getLogger(clazz);
+        }
+
+        public LoggingValidationAware(Object obj) {
+            log = LogManager.getLogger(obj.getClass());
+        }
+
+        public void setActionErrors(Collection<String> errorMessages) {
+            for (Object errorMessage : errorMessages) {
+                String s = (String) errorMessage;
+                addActionError(s);
+            }
+        }
+
+        public Collection<String> getActionErrors() {
+            return null;
+        }
+
+        public void setActionMessages(Collection<String> messages) {
+            for (Object message : messages) {
+                String s = (String) message;
+                addActionMessage(s);
+            }
+        }
+
+        public Collection<String> getActionMessages() {
+            return null;
+        }
+
+        public void setFieldErrors(Map<String, List<String>> errorMap) {
+            for (Map.Entry<String, List<String>> entry : errorMap.entrySet()) {
+                addFieldError(entry.getKey(), entry.getValue().toString());
+            }
+        }
+
+        public Map<String, List<String>> getFieldErrors() {
+            return null;
+        }
+
+        public void addActionError(String anErrorMessage) {
+            log.error("Validation error: {}", anErrorMessage);
+        }
+
+        public void addActionMessage(String aMessage) {
+            log.info("Validation Message: {}", aMessage);
+        }
+
+        public void addFieldError(String fieldName, String errorMessage) {
+            log.error("Validation error for {}:{}", fieldName, errorMessage);
+        }
+
+        public boolean hasActionErrors() {
+            return false;
+        }
+
+        public boolean hasActionMessages() {
+            return false;
+        }
+
+        public boolean hasErrors() {
+            return false;
+        }
+
+        public boolean hasFieldErrors() {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java b/core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java
new file mode 100644
index 0000000..3077671
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/FieldValidator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+/**
+ * The FieldValidator interface defines the methods to be implemented by FieldValidators.
+ * Which are used by the XWork validation framework to validate Action properties before
+ * executing the Action.
+ */
+public interface FieldValidator extends Validator {
+
+    /**
+     * Sets the field name to validate with this FieldValidator
+     *
+     * @param fieldName the field name
+     */
+    void setFieldName(String fieldName);
+
+    /**
+     * Gets the field name to be validated
+     *
+     * @return the field name
+     */
+    String getFieldName();
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java b/core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java
new file mode 100644
index 0000000..651270d
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/ShortCircuitableValidator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ * 
+ * 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 com.opensymphony.xwork2.validator;
+
+
+/**
+ * This interface should be implemented by validators that can short-circuit the validator queue
+ * that it is in.
+ *
+ * @author Mark Woon
+ */
+public interface ShortCircuitableValidator {
+
+    /**
+     * Sets whether this field validator should short circuit the validator queue
+     * it's in if validation fails.
+     *
+     * @param shortcircuit <tt>true</tt> if this field validator should short circuit on
+     *                     failure, <tt>false</tt> otherwise
+     */
+    public void setShortCircuit(boolean shortcircuit);
+
+    /**
+     * Gets whether this field validator should short circuit the validator queue
+     * it's in if validation fails.
+     *
+     * @return <tt>true</tt> if this field validator should short circuit on failure,
+     *         <tt>false</tt> otherwise
+     */
+    public boolean isShortCircuit();
+}