Return-Path: X-Original-To: apmail-struts-commits-archive@minotaur.apache.org Delivered-To: apmail-struts-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id A9D7D18F17 for ; Wed, 17 Jun 2015 21:09:13 +0000 (UTC) Received: (qmail 76456 invoked by uid 500); 17 Jun 2015 21:09:03 -0000 Delivered-To: apmail-struts-commits-archive@struts.apache.org Received: (qmail 76370 invoked by uid 500); 17 Jun 2015 21:09:03 -0000 Mailing-List: contact commits-help@struts.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@struts.apache.org Delivered-To: mailing list commits@struts.apache.org Received: (qmail 75011 invoked by uid 99); 17 Jun 2015 21:09:02 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 17 Jun 2015 21:09:02 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 67F1FDFFC0; Wed, 17 Jun 2015 21:09:02 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: lukaszlenart@apache.org To: commits@struts.apache.org Date: Wed, 17 Jun 2015 21:09:30 -0000 Message-Id: <932e2c704b60478a92a7fc5dedcf2b72@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [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; + +/** + * AnnotationValidationConfigurationBuilder + * + * @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 processAnnotations(Object o) { + + List 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 buildAnnotationClassValidatorConfigs(Class aClass) { + + List result = new ArrayList<>(); + + List 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. + *

+ * Validation rules are specified in XML configuration files named className-contextName-validation.xml 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> validatorCache = Collections.synchronizedMap(new HashMap>()); + private final Map> validatorFileCache = Collections.synchronizedMap(new HashMap>()); + 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 getValidators(Class clazz, String context) { + return getValidators(clazz, context, null); + } + + public synchronized List 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 cfgs = validatorCache.get(validatorKey); + + // create clean instances of the validators for the caller's use + ArrayList 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 validators = getValidators(object.getClass(), context, method); + Set 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 errs = null; + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection fieldErrors = validatorContext.getFieldErrors().get(fullFieldName); + + if (fieldErrors != null) { + errs = new ArrayList<>(fieldErrors); + } + } + } else if (validatorContext.hasActionErrors()) { + Collection actionErrors = validatorContext.getActionErrors(); + + if (actionErrors != null) { + errs = new ArrayList(actionErrors); + } + } + + validator.validate(object); + + if (fValidator != null) { + if (validatorContext.hasFieldErrors()) { + Collection 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 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 buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) { + String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX; + + return loadFile(fileName, aClass, checkFile); + } + + private List buildClassValidatorConfigs(Class aClass, boolean checkFile) { + String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX; + + return loadFile(fileName, aClass, checkFile); + } + + /** + *

This method 'collects' all the validator configurations for a given + * action invocation.

+ * + *

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!

+ * + *

Given the following class structure: + *

+     *   interface Thing;
+     *   interface Animal extends Thing;
+     *   interface Quadraped extends Animal;
+     *   class AnimalImpl implements Animal;
+     *   class QuadrapedImpl extends AnimalImpl implements Quadraped;
+     *   class Dog extends QuadrapedImpl;
+     * 

+ * + *

This method will look for the following config files for Dog: + *

+     *   Animal
+     *   Animal-context
+     *   AnimalImpl
+     *   AnimalImpl-context
+     *   Quadraped
+     *   Quadraped-context
+     *   QuadrapedImpl
+     *   QuadrapedImpl-context
+     *   Dog
+     *   Dog-context
+     * 

+ * + *

Note that the validation rules for Thing is never looked for because no class in the + * hierarchy directly implements Thing.

+ * + * @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 buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set checked) { + List validatorConfigs = new ArrayList<>(); + + if (checked == null) { + checked = new TreeSet(); + } 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 loadFile(String fileName, Class clazz, boolean checkFile) { + List 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 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 files = new ArrayList<>(); + try { + // Get custom validator configurations via the classpath + Iterator 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 parseActionValidatorConfigs(ValidatorFactory validatorFactory, InputStream is, final String resourceName) { + List validatorCfgs = new ArrayList(); + + InputSource in = new InputSource(is); + in.setSystemId(resourceName); + + Map dtdMappings = new HashMap(); + 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(), validatorCfgs); + } + + for (int i = 0; i < fieldNodes.getLength(); i++) { + Element fieldElement = (Element) fieldNodes.item(i); + String fieldName = fieldElement.getAttribute("name"); + Map extraParams = new HashMap(); + extraParams.put("fieldName", fieldName); + + NodeList validatorNodes = fieldElement.getElementsByTagName("field-validator"); + addValidatorConfigs(validatorFactory, validatorNodes, extraParams, validatorCfgs); + } + } + + return validatorCfgs; + } + + + public void parseValidatorDefinitions(Map validators, InputStream is, String resourceName) { + + InputSource in = new InputSource(is); + in.setSystemId(resourceName); + + Map dtdMappings = new HashMap(); + 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(), 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 extraParams, List validatorCfgs) { + for (int j = 0; j < validatorNodes.getLength(); j++) { + Element validatorElement = (Element) validatorNodes.item(j); + String validatorType = validatorElement.getAttribute("type"); + Map params = new HashMap(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 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: + // Default message + // pattern 2: + // + // 'param1' + // 'param2' + // sortedMessageParameters = new TreeMap(); + for (Map.Entry 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 errorMessages) { + validationAware.setActionErrors(errorMessages); + } + + public Collection getActionErrors() { + return validationAware.getActionErrors(); + } + + public void setActionMessages(Collection messages) { + validationAware.setActionMessages(messages); + } + + public Collection getActionMessages() { + return validationAware.getActionMessages(); + } + + public void setFieldErrors(Map> errorMap) { + validationAware.setFieldErrors(errorMap); + } + + public Map> 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 errorMessages) { + for (Object errorMessage : errorMessages) { + String s = (String) errorMessage; + addActionError(s); + } + } + + public Collection getActionErrors() { + return null; + } + + public void setActionMessages(Collection messages) { + for (Object message : messages) { + String s = (String) message; + addActionMessage(s); + } + } + + public Collection getActionMessages() { + return null; + } + + public void setFieldErrors(Map> errorMap) { + for (Map.Entry> entry : errorMap.entrySet()) { + addFieldError(entry.getKey(), entry.getValue().toString()); + } + } + + public Map> 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 true if this field validator should short circuit on + * failure, false otherwise + */ + public void setShortCircuit(boolean shortcircuit); + + /** + * Gets whether this field validator should short circuit the validator queue + * it's in if validation fails. + * + * @return true if this field validator should short circuit on failure, + * false otherwise + */ + public boolean isShortCircuit(); +}