diff --git a/core/src/main/java/org/apache/struts2/components/Form.java b/core/src/main/java/org/apache/struts2/components/Form.java index aec4e4abe..ad8250d8a 100644 --- a/core/src/main/java/org/apache/struts2/components/Form.java +++ b/core/src/main/java/org/apache/struts2/components/Form.java @@ -31,16 +31,22 @@ import com.opensymphony.xwork2.interceptor.MethodFilterInterceptorUtil; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.validator.ActionValidatorManager; import com.opensymphony.xwork2.validator.FieldValidator; +import com.opensymphony.xwork2.validator.ValidationException; import com.opensymphony.xwork2.validator.ValidationInterceptor; import com.opensymphony.xwork2.validator.Validator; -import org.apache.struts2.StrutsConstants; +import com.opensymphony.xwork2.validator.ValidatorContext; +import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator; +import org.apache.commons.lang3.StringUtils; +import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; -import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.*; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Set; /** @@ -116,14 +122,17 @@ public class Form extends ClosingUIBean { super(stack, request, response); } + @Override protected boolean evaluateNameValue() { return false; } + @Override public String getDefaultOpenTemplate() { return OPEN_TEMPLATE; } + @Override protected String getDefaultTemplate() { return TEMPLATE; } @@ -153,6 +162,7 @@ public class Form extends ClosingUIBean { * Revised for Portlet actionURL as form action, and add wwAction as hidden * field. Refer to template.simple/form.vm */ + @Override protected void evaluateExtraParams() { super.evaluateExtraParams(); if (validate != null) { @@ -210,6 +220,7 @@ public class Form extends ClosingUIBean { *
  • if an 'action' attribute is specified, it will be used as the id.
  • * */ + @Override protected void populateComponentHtmlId(Form form) { if (id != null) { addParameter("id", escape(id)); @@ -262,20 +273,144 @@ public class Form extends ClosingUIBean { return Collections.EMPTY_LIST; } - List all = actionValidatorManager.getValidators(actionClass, (String) getParameters().get("actionName")); + String formActionValue = findString(action); + ActionMapping mapping = actionMapper.getMappingFromActionName(formActionValue); + String actionName = mapping.getName(); + String methodName = mapping.getMethod(); + + List actionValidators = actionValidatorManager.getValidators(actionClass, actionName, methodName); List validators = new ArrayList(); - for (Validator validator : all) { - if (validator instanceof FieldValidator) { - FieldValidator fieldValidator = (FieldValidator) validator; - if (fieldValidator.getFieldName().equals(name)) { - validators.add(fieldValidator); - } - } - } + + findFieldValidators(name, actionClass, actionName, actionValidators, validators, ""); return validators; } + private void findFieldValidators(String name, Class actionClass, String actionName, + List validatorList, List retultValidators, String prefix) { + + for (Validator validator : validatorList) { + if (validator instanceof FieldValidator) { + FieldValidator fieldValidator = (FieldValidator) validator; + + if (validator instanceof VisitorFieldValidator) { + VisitorFieldValidator vfValidator = (VisitorFieldValidator) fieldValidator; + Class clazz = getVisitorReturnType(actionClass, vfValidator.getFieldName()); + if (clazz == null) { + continue; + } + + List visitorValidators = actionValidatorManager.getValidators(clazz, actionName); + String vPrefix = prefix + (vfValidator.isAppendPrefix() ? vfValidator.getFieldName() + "." : ""); + findFieldValidators(name, clazz, actionName, visitorValidators, retultValidators, vPrefix); + } else if ((prefix + fieldValidator.getFieldName()).equals(name)) { + if (StringUtils.isNotBlank(prefix)) { + //fixing field name for js side + FieldVisitorValidatorWrapper wrap = new FieldVisitorValidatorWrapper(fieldValidator, prefix); + retultValidators.add(wrap); + } else { + retultValidators.add(fieldValidator); + } + } + } + } + } + + /** + * Wrap field validator, add visitor's field prefix to the field name. + * Javascript side is not aware of the visitor validators + * and does not know how to prefix the fields. + */ + /* + * Class is public because Freemarker has problems accessing properties. + */ + public static class FieldVisitorValidatorWrapper implements FieldValidator { + private FieldValidator fieldValidator; + private String namePrefix; + public FieldVisitorValidatorWrapper(FieldValidator fv, String namePrefix) { + this.fieldValidator = fv; + this.namePrefix = namePrefix; + } + public String getValidatorType() { + return "field-visitor"; + } + public String getFieldName() { + return namePrefix + fieldValidator.getFieldName(); + } + public FieldValidator getFieldValidator() { + return fieldValidator; + } + public void setFieldValidator(FieldValidator fieldValidator) { + this.fieldValidator = fieldValidator; + } + public String getDefaultMessage() { + return fieldValidator.getDefaultMessage(); + } + public String getMessage(Object object) { + return fieldValidator.getMessage(object); + } + public String getMessageKey() { + return fieldValidator.getMessageKey(); + } + public String[] getMessageParameters() { + return fieldValidator.getMessageParameters(); + } + public ValidatorContext getValidatorContext() { + return fieldValidator.getValidatorContext(); + } + public void setDefaultMessage(String message) { + fieldValidator.setDefaultMessage(message); + } + public void setFieldName(String fieldName) { + fieldValidator.setFieldName(fieldName); + } + public void setMessageKey(String key) { + fieldValidator.setMessageKey(key); + } + public void setMessageParameters(String[] messageParameters) { + fieldValidator.setMessageParameters(messageParameters); + } + public void setValidatorContext(ValidatorContext validatorContext) { + fieldValidator.setValidatorContext(validatorContext); + } + public void setValidatorType(String type) { + fieldValidator.setValidatorType(type); + } + public void setValueStack(ValueStack stack) { + fieldValidator.setValueStack(stack); + } + public void validate(Object object) throws ValidationException { + fieldValidator.validate(object); + } + public String getNamePrefix() { + return namePrefix; + } + public void setNamePrefix(String namePrefix) { + this.namePrefix = namePrefix; + } + } + + /** + * Return type of visited object. + * @param actionClass + * @param visitorFieldName + * @return + */ + @SuppressWarnings("unchecked") + protected Class getVisitorReturnType(Class actionClass, String visitorFieldName) { + if (visitorFieldName == null) { + return null; + } + String methodName = "get" + org.apache.commons.lang.StringUtils.capitalize(visitorFieldName); + try { + Method method = actionClass.getMethod(methodName, new Class[0]); + return method.getReturnType(); + } catch (NoSuchMethodException e) { + return null; + } + } + + /** * Get a incrementing sequence unique to this Form component. * It is used by Form component's child that might need a diff --git a/core/src/main/resources/template/xhtml/form-close-validate.ftl b/core/src/main/resources/template/xhtml/form-close-validate.ftl index fcf416b1a..81a17e1e1 100644 --- a/core/src/main/resources/template/xhtml/form-close-validate.ftl +++ b/core/src/main/resources/template/xhtml/form-close-validate.ftl @@ -36,6 +36,26 @@ END SNIPPET: supported-validators <#if ((parameters.validate!false == true) && (parameters.performValidation!false == true))> - + \ No newline at end of file diff --git a/core/src/test/java/org/apache/struts2/components/FormTest.java b/core/src/test/java/org/apache/struts2/components/FormTest.java index f58829135..f1611f2a9 100644 --- a/core/src/test/java/org/apache/struts2/components/FormTest.java +++ b/core/src/test/java/org/apache/struts2/components/FormTest.java @@ -21,18 +21,17 @@ package org.apache.struts2.components; -import java.util.List; - +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator; import org.apache.struts2.TestAction; import org.apache.struts2.views.jsp.AbstractUITagTest; import org.easymock.EasyMock; -import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator; -import com.opensymphony.xwork2.config.entities.ActionConfig; -import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.Action; -import com.opensymphony.xwork2.ActionContext; +import java.util.List; /** * FormTest @@ -45,6 +44,7 @@ public class FormTest extends AbstractUITagTest { Form form = new Form(stack, request, response); container.inject(form); form.getParameters().put("actionClass", TestAction.class); + form.setAction("actionName"); List v = form.getValidators("foo"); assertEquals(1, v.size()); assertEquals(RequiredFieldValidator.class, v.get(0).getClass()); diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt index b31d320d8..1a4678cf8 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt @@ -32,12 +32,32 @@ diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt index bb6bbaa43..ee76c3a4c 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt @@ -33,23 +33,41 @@ - + //fieldname:myUpDownSelectTag + //validatorname:int + if(form.elements['myUpDownSelectTag']) { + field = form.elements['myUpDownSelectTag']; + var error = "bar must be between 6000 and 10000."; + var fieldValue = getFieldValue(field); + if(continueValidation && fieldValue != null) { + if(parseInt(fieldValue) < 6000 || parseInt(fieldValue) > 10000) { + addError(field, error); + errors = true; + } + } + } + return !errors; + } + \ No newline at end of file diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt index 5c47bd28f..7888a5302 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt @@ -33,24 +33,43 @@ + //fieldname:myUpDownSelectTag + //validatorname:double + if(form.elements['myUpDownSelectTag']) { + field = form.elements['myUpDownSelectTag']; + var error = "bar must be between 6000.1 and 10000.1."; + var fieldValue = getFieldValue(field); + if(continueValidation && fieldValue != null) { + var value = parseFloat(fieldValue); + if(value < 6000.1 || value > 10000.1 || false || false) { + addError(field, error); + errors = true; + } + } + } + return !errors; + } +