1. opensymphony
  2. xwork

Commits

rainerh  committed a56cb82

XW-593 Parametizing i18n messages for validators
o ported Toby's i18n impl for validators from 1.2 branch

git-svn-id: http://svn.opensymphony.com/svn/xwork/trunk@1799e221344d-f017-0410-9bd5-d282ab1896d7

  • Participants
  • Parent commits 7584f18
  • Branches master

Comments (0)

Files changed (9)

File src/java/com/opensymphony/xwork2/validator/DefaultValidatorFactory.java

View file
 import java.io.FilenameFilter;
 import java.io.InputStream;
 import java.io.IOException;
-import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URI;
 import java.util.*;
         // 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());
         }

File src/java/com/opensymphony/xwork2/validator/DefaultValidatorFileParser.java

View file
 
 import com.opensymphony.xwork2.ObjectFactory;
 import com.opensymphony.xwork2.util.DomHelper;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
 import com.opensymphony.xwork2.XWorkException;
 import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.providers.XmlHelper;
 import com.opensymphony.xwork2.inject.Inject;
 import org.w3c.dom.*;
 import org.xml.sax.InputSource;
 
 import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 
 /**
  * @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 = LoggerFactory.getLogger(DefaultValidatorFileParser.class);
+
     static final String MULTI_TEXTVALUE_SEPARATOR = " ";
     private ObjectFactory objectFactory;
 
 
         InputSource in = new InputSource(is);
         in.setSystemId(resourceName);
-            
+
         Map dtdMappings = new HashMap();
         dtdMappings.put("-//OpenSymphony Group//XWork Validator 1.0//EN", "xwork-validator-1.0.dtd");
         dtdMappings.put("-//OpenSymphony Group//XWork Validator 1.0.2//EN", "xwork-validator-1.0.2.dtd");
-        
+        dtdMappings.put("-//OpenSymphony Group//XWork Validator 1.0.3//EN", "xwork-validator-1.0.3.dtd");
+
         doc = DomHelper.parse(in, dtdMappings);
 
         if (doc != null) {
 
             // 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-leve validator to be kicked off.
-            {NodeList validatorNodes = doc.getElementsByTagName("validator");
-            addValidatorConfigs(validatorFactory, validatorNodes, new HashMap(), validatorCfgs);}
+            // 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);
         return validatorCfgs;
     }
 
-    
-    public void parseValidatorDefinitions(Map<String,String> validators, InputStream is, String resourceName) {
+
+    public void parseValidatorDefinitions(Map<String, String> validators, InputStream is, String resourceName) {
 
         InputSource in = new InputSource(is);
         in.setSystemId(resourceName);
-            
+
         Map dtdMappings = new HashMap();
         dtdMappings.put("-//OpenSymphony Group//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd");
 
 
         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");
             Element validatorElement = (Element) validatorNodes.item(j);
             String validatorType = validatorElement.getAttribute("type");
             Map params = new HashMap(extraParams);
-            NodeList paramNodes = validatorElement.getElementsByTagName("param");
 
-            for (int k = 0; k < paramNodes.getLength(); k++) {
-                Element paramElement = (Element) paramNodes.item(k);
-                String paramName = paramElement.getAttribute("name");
-                params.put(paramName, getTextValue(paramElement));
-            }
+            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);
+                throw new ConfigurationException("Invalid validation type: " + validatorType, validatorElement);
             }
 
             ValidatorConfig.Builder vCfg = new ValidatorConfig.Builder(validatorType)
 
             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:
+                // <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((String) messageParams.get("defaultMessage"));
+                }
+
+                // Sort the message param. those with keys as '1', '2', '3' etc. (numeric values)
+                // are i18n message parameter, others are excluded.
+                TreeMap sortedMessageParameters = new TreeMap();
+                for (Iterator i = messageParams.entrySet().iterator(); i.hasNext();) {
+                    Map.Entry messageParamEntry = (Map.Entry) i.next();
+                    try {
+                        int _order = Integer.parseInt((String) messageParamEntry.getKey());
+                        sortedMessageParameters.put(new Integer(_order), messageParamEntry.getValue());
+                    }
+                    catch (NumberFormatException e) {
+                        // ignore if its not numeric.
+                    }
+                }
+                vCfg.messageParams((String[]) sortedMessageParameters.values().toArray(new String[0]));
+            } else {
+                if (messageParams != null && (messageParams.size() > 0)) {
+                    // we are i18n message parameters defined but no i18n message,
+                    // let's warn the user.
+                    LOG.warn("validator of type ["+validatorType+"] have i18n message parameters defined but no i18n message key, it's parameters will be ignored");
+                }
             }
 
-            final Node defaultMessageNode = messageElement.getFirstChild();
-            String defaultMessage = (defaultMessageNode == null) ? "" : defaultMessageNode.getNodeValue();
-            vCfg.defaultMessage(defaultMessage);
             validatorCfgs.add(vCfg.build());
         }
     }

File src/java/com/opensymphony/xwork2/validator/Validator.java

View file
  * <!-- START SNIPPET: validatorFlavours -->
  * <p>The validators supplied by the XWork distribution (and any validators you
  * might write yourself) come in two different flavors:</p>
- *
+ * <p/>
  * <ol>
- *  <li> Plain Validators / Non-Field validators </li>
- *  <li> FieldValidators </li>
+ * <li> Plain Validators / Non-Field validators </li>
+ * <li> FieldValidators </li>
  * </ol>
- *
- * <p>Plain Validators (such as the ExpressionValidator) perform validation checks 
- * that are not inherently tied to a single specified field. When you declare a 
- * plain Validator in your -validation.xml file you do not associate a fieldname 
- * attribute with it. (You should avoid using plain Validators within the 
+ * <p/>
+ * <p>Plain Validators (such as the ExpressionValidator) perform validation checks
+ * that are not inherently tied to a single specified field. When you declare a
+ * plain Validator in your -validation.xml file you do not associate a fieldname
+ * attribute with it. (You should avoid using plain Validators within the
  * <field-validator> syntax described below.)</p>
- *
- * <p>FieldValidators (such as the EmailValidator) are designed to perform 
- * validation checks on a single field. They require that you specify a fieldname 
- * attribute in your -validation.xml file. There are two different (but equivalent) 
- * XML syntaxes you can use to declare FieldValidators (see "<validator> vs. 
+ * <p/>
+ * <p>FieldValidators (such as the EmailValidator) are designed to perform
+ * validation checks on a single field. They require that you specify a fieldname
+ * attribute in your -validation.xml file. There are two different (but equivalent)
+ * XML syntaxes you can use to declare FieldValidators (see "<validator> vs.
  * <field-Validator> syntax" below).</p>
- *
- * <p>There are two places where the differences between the two validator flavors 
+ * <p/>
+ * <p>There are two places where the differences between the two validator flavors
  * are important to keep in mind:</p>
- *
+ * <p/>
  * <ol>
- *   <li> when choosing the xml syntax used for declaring a validator 
- *       (either <validator> or <field-validator>)</li>
- *   <li> when using the short-circuit capability</li>
+ * <li> when choosing the xml syntax used for declaring a validator
+ * (either <validator> or <field-validator>)</li>
+ * <li> when using the short-circuit capability</li>
  * </ol>
- *
- * <p><b>NOTE:</b>Note that you do not declare what "flavor" of validator you are 
- * using in your -validation.xml file, you just declare the name of the validator 
- * to use and Struts will know whether it's a "plain Validator" or a "FieldValidator"  
- * by looking at the validation class that the validator's programmer chose 
+ * <p/>
+ * <p><b>NOTE:</b>Note that you do not declare what "flavor" of validator you are
+ * using in your -validation.xml file, you just declare the name of the validator
+ * to use and Struts will know whether it's a "plain Validator" or a "FieldValidator"
+ * by looking at the validation class that the validator's programmer chose
  * to implement.</p>
  * <!-- END SNIPPET: validatorFlavours -->
- * 
- * 
- * 
- * 
+ * <p/>
+ * <p/>
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: validationRules -->
- * <p>To define validation rules for an Action, create a file named ActionName-validation.xml 
- * in the same package as the Action. You may also create alias-specific validation rules which 
- * add to the default validation rules defined in ActionName-validation.xml by creating 
- * another file in the same directory named ActionName-aliasName-validation.xml. In both 
- * cases, ActionName is the name of the Action class, and aliasName is the name of the 
+ * <p>To define validation rules for an Action, create a file named ActionName-validation.xml
+ * in the same package as the Action. You may also create alias-specific validation rules which
+ * add to the default validation rules defined in ActionName-validation.xml by creating
+ * another file in the same directory named ActionName-aliasName-validation.xml. In both
+ * cases, ActionName is the name of the Action class, and aliasName is the name of the
  * Action alias defined in the xwork.xml configuration for the Action.</p>
- * 
- * <p>The framework will also search up the inheritance tree of the Action to 
- * find validation rules for directly implemented interfaces and parent classes of the Action. 
- * This is particularly powerful when combined with ModelDriven Actions and the VisitorFieldValidator. 
+ * <p/>
+ * <p>The framework will also search up the inheritance tree of the Action to
+ * find validation rules for directly implemented interfaces and parent classes of the Action.
+ * This is particularly powerful when combined with ModelDriven Actions and the VisitorFieldValidator.
  * Here's an example of how validation rules are discovered. Given the following class structure:</p>
- * 
+ * <p/>
  * <ul>
- *   <li>interface Animal;</li>
- *   <li>interface Quadraped extends Animal;</li>
- *   <li>class AnimalImpl implements Animal;</li>
- *   <li>class QuadrapedImpl extends AnimalImpl implements Quadraped;</li>
- *   <li>class Dog extends QuadrapedImpl;</li>
+ * <li>interface Animal;</li>
+ * <li>interface Quadraped extends Animal;</li>
+ * <li>class AnimalImpl implements Animal;</li>
+ * <li>class QuadrapedImpl extends AnimalImpl implements Quadraped;</li>
+ * <li>class Dog extends QuadrapedImpl;</li>
  * </ul>
- * 
+ * <p/>
  * <p>The framework method will look for the following config files if Dog is to be validated:</p>
- *
+ * <p/>
  * <ul>
- *   <li>Animal</li>
- *   <li>Animal-aliasname</li>
- *   <li>AnimalImpl</li>
- * 	 <li>AnimalImpl-aliasname</li>
- *   <li>Quadraped</li>
- *   <li>Quadraped-aliasname</li>
- *   <li>QuadrapedImpl</li>
- *   <li>QuadrapedImpl-aliasname</li>
- *   <li>Dog</li>
- *   <li>Dog-aliasname</li>
+ * <li>Animal</li>
+ * <li>Animal-aliasname</li>
+ * <li>AnimalImpl</li>
+ * <li>AnimalImpl-aliasname</li>
+ * <li>Quadraped</li>
+ * <li>Quadraped-aliasname</li>
+ * <li>QuadrapedImpl</li>
+ * <li>QuadrapedImpl-aliasname</li>
+ * <li>Dog</li>
+ * <li>Dog-aliasname</li>
  * </ul>
- *
- * <p>While this process is similar to what the XW:Localization framework does 
- * when finding messages, there are some subtle differences. The most important 
+ * <p/>
+ * <p>While this process is similar to what the XW:Localization framework does
+ * when finding messages, there are some subtle differences. The most important
  * difference is that validation rules are discovered from the parent downwards.
  * </p>
- * 
- * <p><b>NOTE:</b>Child's *-validation.xml will add on to parent's *-validation.xml 
+ * <p/>
+ * <p><b>NOTE:</b>Child's *-validation.xml will add on to parent's *-validation.xml
  * according to the class hierarchy defined above. With this feature, one could have
  * more generic validation rule at the parent and more specific validation rule at
  * the child.</p>
- * 
+ * <p/>
  * <!-- END SNIPPET: validationRules -->
- * 
- * 
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: validatorVsFieldValidators1 -->
  * <p>There are two ways you can define validators in your -validation.xml file:</p>
  * <ol>
- *  <li> &lt;validator&gt; </li>
- *  <li> &lt;field-validator&gt; </li>
+ * <li> &lt;validator&gt; </li>
+ * <li> &lt;field-validator&gt; </li>
  * </ol>
  * <p>Keep the following in mind when using either syntax:</p>
- * 
+ * <p/>
  * <p><b>Non-Field-Validator</b>
- * The &lt;validator&gt; element allows you to declare both types of validators 
+ * The &lt;validator&gt; element allows you to declare both types of validators
  * (either a plain Validator a field-specific FieldValidator).</p>
  * <!-- END SNIPPET: validatorVsFieldValidators1 -->
- *
- *<pre>
+ * <p/>
+ * <pre>
  * <!-- START SNIPPET: nonFieldValidatorUsingValidatorSyntax -->
  *    &lt;!-- Declaring a plain Validator using the &lt;validator&gt; syntax: --&gt;
- *
+ * <p/>
  *    &lt;validator type="expression&gt;
  *          &lt;param name="expression">foo gt bar&lt;/param&gt;
  *          &lt;message&gt;foo must be great than bar.&lt;/message&gt;
  *    &lt;/validator&gt;
  * <!-- END SNIPPET: nonFieldValidatorUsingValidatorSyntax -->
  * </pre>
- * 
+ * <p/>
  * <pre>
  * <!-- START SNIPPET: fieldValidatorUsingValidatorSyntax -->
  *    &lt;!-- Declaring a field validator using the &lt;validator&gt; syntax; --&gt;
- *
+ * <p/>
  *    &lt;validator type="required"&gt;
  *         &lt;param name="fieldName"&gt;bar&lt;/param&gt;
  *         &lt;message&gt;You must enter a value for bar.&lt;/message&gt;
  *    &lt/validator&gt;
  * <!-- END SNIPPET: fieldValidatorUsingValidatorSyntax -->
  * </pre>
- *
- *
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: validatorVsFieldValidators2 -->
  * <p><b>field-validator</b>
- * The &lt;field-validator&gt; elements are basically the same as the &lt;validator&gt; elements 
- * except that they inherit the fieldName attribute from the enclosing &lt;field&gt; element. 
- * FieldValidators defined within a &lt;field-validator&gt; element will have their fieldName 
- * automatically filled with the value of the parent &lt;field&gt; element's fieldName 
- * attribute. The reason for this structure is to conveniently group the validators 
- * for a particular field under one element, otherwise the fieldName attribute 
+ * The &lt;field-validator&gt; elements are basically the same as the &lt;validator&gt; elements
+ * except that they inherit the fieldName attribute from the enclosing &lt;field&gt; element.
+ * FieldValidators defined within a &lt;field-validator&gt; element will have their fieldName
+ * automatically filled with the value of the parent &lt;field&gt; element's fieldName
+ * attribute. The reason for this structure is to conveniently group the validators
+ * for a particular field under one element, otherwise the fieldName attribute
  * would have to be repeated, over and over, for each individual &lt;validator&gt;.</p>
- * 
+ * <p/>
  * <p><b>HINT:</b>
- * It is always better to defined field-validator inside a &lt;field&gt; tag instead of 
- * using a &lt;validator&gt; tag and supplying fieldName as its param as the xml code itself 
+ * It is always better to defined field-validator inside a &lt;field&gt; tag instead of
+ * using a &lt;validator&gt; tag and supplying fieldName as its param as the xml code itself
  * is clearer (grouping of field is clearer)</p>
- * 
+ * <p/>
  * <p><b>NOTE:</b>
- * Note that you should only use FieldValidators (not plain Validators) within a 
- * <field-validator> block. A plain Validator inside a &lt;field&gt; will not be 
- * allowed and would generate error when parsing the xml, as it is not allowed in 
+ * Note that you should only use FieldValidators (not plain Validators) within a
+ * <field-validator> block. A plain Validator inside a &lt;field&gt; will not be
+ * allowed and would generate error when parsing the xml, as it is not allowed in
  * the defined dtd (xwork-validator-1.0.2.dtd)</p>
  * <!-- END SNIPPET: validatorVsFieldValidators2 -->
- *
+ * <p/>
  * <pre>
  * <!-- START SNIPPET: fieldValidatorUsingFieldValidatorSyntax -->
  * Declaring a FieldValidator using the &lt;field-validator&gt; syntax:
- * 
+ * <p/>
  * &lt;field name="email_address"&gt;
  *   &lt;field-validator type="required"&gt;
  *       &lt;message&gt;You cannot leave the email address field empty.&lt;/message&gt;
  * &lt;/field&gt;
  * <!-- END SNIPPET: fieldValidatorUsingFieldValidatorSyntax -->
  * </pre>
- * 
- * 
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: validatorVsFieldValidators3 -->
- * <p>The choice is yours. It's perfectly legal to only use <validator> elements 
- * without the <field> elements and set the fieldName attribute for each of them. 
+ * <p>The choice is yours. It's perfectly legal to only use <validator> elements
+ * without the <field> elements and set the fieldName attribute for each of them.
  * The following are effectively equal:</P>
  * <!-- END SNIPPET: validatorVsFieldValidators3 -->
- * 
+ * <p/>
  * <pre>
  * <!-- START-SNIPPET: similarVaidatorDeclaredInDiffSyntax -->
  * &lt;field name="email_address"&gt;
  *       &lt;message&gt;The email address you entered is not valid.&lt;/message&gt;
  *   &lt;/field-validator&gt;
  * &lt;/field&gt;
- *
- *
+ * <p/>
+ * <p/>
  * &lt;validator type="required"&gt;
  *   &lt;param name="fieldName"&gt;email_address&lt;/param&gt;
  *   &lt;message&gt;You cannot leave the email address field empty.&lt;/message&gt;
  * &lt;/validator&gt;
  * <!-- END SNIPPET: similarVaidatorDeclaredInDiffSyntax -->
  * </pre>
- *
- *
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: shortCircuitingValidators1 -->
- * <p>It is possible to short-circuit a stack of validators. 
+ * <p>It is possible to short-circuit a stack of validators.
  * Here is another sample config file containing validation rules from the
  * Xwork test cases: Notice that some of the &lt;field-validator&gt; and
  * &lt;validator&gt; elements have the short-circuit attribute set to true.</p>
  * <!-- END SNIPPET : shortCircuitingValidators1 -->
- *
- *<pre>
+ * <p/>
+ * <pre>
  * &lt;!-- START SNIPPET: exShortCircuitingValidators --&gt;
- * &lt;!DOCTYPE validators PUBLIC 
- *         "-//OpenSymphony Group//XWork Validator 1.0.2//EN" 
+ * &lt;!DOCTYPE validators PUBLIC
+ *         "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
  *         "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"&gt;
  * &lt;validators&gt;
  *   &lt;!-- Field Validators for email field --&gt;
  *   &lt;/validator&gt;
  * &lt;/validators&gt;
  * &lt;!-- END SNIPPET: exShortCircuitingValidators --&gt;
- *</pre>
- *
+ * </pre>
+ * <p/>
  * <!-- START SNIPPET:shortCircuitingValidators2  -->
  * <p><b>short-circuiting and Validator flavors</b></p>
- * <p>Plain validator takes precedence over field-validator. They get validated 
- * first in the order they are defined and then the field-validator in the order 
- * they are defined. Failure of a particular validator marked as short-circuit 
- * will prevent the evaluation of subsequent validators and an error (action 
- * error or field error depending on the type of validator) will be added to 
+ * <p>Plain validator takes precedence over field-validator. They get validated
+ * first in the order they are defined and then the field-validator in the order
+ * they are defined. Failure of a particular validator marked as short-circuit
+ * will prevent the evaluation of subsequent validators and an error (action
+ * error or field error depending on the type of validator) will be added to
  * the ValidationContext of the object being validated.</p>
- *
+ * <p/>
  * <p>In the example above, the actual execution of validator would be as follows:</p>
- * 
+ * <p/>
  * <ol>
- *  <li> Plain Validator 1</li>
- *  <li> Plain Validator 2</li>
- *  <li> Field Validators for email field</li>
- *  <li> Field Validators for email2 field</li>
+ * <li> Plain Validator 1</li>
+ * <li> Plain Validator 2</li>
+ * <li> Field Validators for email field</li>
+ * <li> Field Validators for email2 field</li>
  * </ol>
- *
- * <p>Since Plain Validator 2 is short-circuited, if its validation failed, 
- * it will causes Field validators for email field and Field validators for email2 
+ * <p/>
+ * <p>Since Plain Validator 2 is short-circuited, if its validation failed,
+ * it will causes Field validators for email field and Field validators for email2
  * field to not be validated as well.</p>
- * 
+ * <p/>
  * <p><b>Usefull Information:</b>
- * More complicated validation should probably be done in the validate() 
- * method on the action itself (assuming the action implements Validatable 
+ * More complicated validation should probably be done in the validate()
+ * method on the action itself (assuming the action implements Validatable
  * interface which ActionSupport already does).</p>
- * 
+ * <p/>
  * <p>
  * A plain Validator (non FieldValidator) that gets short-circuited will
- * completely break out of the validation stack. No other validators will be 
+ * completely break out of the validation stack. No other validators will be
  * evaluated and plain validators takes precedence over field validators meaning
  * that they get evaluated in the order they are defined before field validators
  * get a chance to be evaluated.
  * </p>
  * <!-- END SNIPPET: shortCircuitingValidators2 -->
- * 
- * 
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: scAndValidatorFlavours1 -->
  * <p><b>Short cuircuiting and validator flavours</b></p>
- * <p>A FieldValidator that gets short-circuited will only prevent other 
- * FieldValidators for the same field from being evaluated. Note that this 
- * "same field" behavior applies regardless of whether the <validator> or 
- * <field-validator> syntax was used to declare the validation rule. 
+ * <p>A FieldValidator that gets short-circuited will only prevent other
+ * FieldValidators for the same field from being evaluated. Note that this
+ * "same field" behavior applies regardless of whether the <validator> or
+ * <field-validator> syntax was used to declare the validation rule.
  * By way of example, given this -validation.xml file:</p>
  * <!-- END SNIPPET: scAndValidatorFlavours1 -->
- * 
+ * <p/>
  * <pre>
  * <!-- START SNIPPET: exScAndValidatorFlavours -->
  * &lt;validator type="required" short-circuit="true"&gt;
  *   &lt;param name="fieldName"&gt;bar&lt;/param&gt;
  *   &lt;message&gt;You must enter a value for bar.&lt;/message&gt;
  * &lt;/validator&gt;
- *
+ * <p/>
  * &lt;validator type="expression"&gt;
  *   &lt;param name="expression">foo gt bar&lt;/param&gt;
  *   &lt;message&gt;foo must be great than bar.&lt;/message&gt;
  * &lt;/validator&gt;
  * <!-- END SNIPPET: exScAndValidatorFlavours -->
  * </pre>
- * 
+ * <p/>
  * <!-- START SNIPPET: scAndValidatorFlavours2 -->
  * <p>both validators will be run, even if the "required" validator short-circuits.
- * "required" validators are FieldValidator's and will not short-circuit the plain 
- * ExpressionValidator because FieldValidators only short-circuit other checks on 
- * that same field. Since the plain Validator is not field specific, it is 
+ * "required" validators are FieldValidator's and will not short-circuit the plain
+ * ExpressionValidator because FieldValidators only short-circuit other checks on
+ * that same field. Since the plain Validator is not field specific, it is
  * not short-circuited.</p>
  * <!-- END SNIPPET: scAndValidatorFlavours2 -->
- * 
- * 
+ * <p/>
+ * <p/>
  * <!-- START SNIPPET: howXworkFindsValidatorForAction -->
- * <p>As mentioned above, the framework will also search up the inheritance tree 
- * of the action to find default validations for interfaces and parent classes of 
- * the Action. If you are using the short-circuit attribute and relying on 
- * default validators higher up in the inheritance tree, make sure you don't 
+ * <p>As mentioned above, the framework will also search up the inheritance tree
+ * of the action to find default validations for interfaces and parent classes of
+ * the Action. If you are using the short-circuit attribute and relying on
+ * default validators higher up in the inheritance tree, make sure you don't
  * accidentally short-circuit things higher in the tree that you really want!</p>
  * <!-- END SNIPPET: howXworkFindsValidatorForAction -->
- * 
  *
  * @author Jason Carreira
  */
     /**
      * Sets the default message to use for validation failure
      *
-     * @param message  the default message
+     * @param message the default message
      */
     void setDefaultMessage(String message);
 
     /**
      * Gets the validation failure message for the given object
      *
-     * @param object  object being validated (eg. a domain model object)
-     * @return  the validation failure message
+     * @param object object being validated (eg. a domain model object)
+     * @return the validation failure message
      */
     String getMessage(Object object);
 
     /**
      * Sets a resource bundle key to be used for lookup of validation failure message
      *
-     * @param key  the resource bundle key
+     * @param key the resource bundle key
      */
     void setMessageKey(String key);
 
     /**
      * Gets the resource bundle key used for lookup of validation failure message
      *
-     * @return  the resource bundle key
+     * @return the resource bundle key
      */
     String getMessageKey();
 
     /**
+     * Sets the messsage parameters to be used when parsing i18n messages
+     *
+     * @param messageParameters  the messsage parameters
+     */
+    void setMessageParameters(String[] messageParameters);
+
+    /**
+     * Gets the messsage parameters to be used when parsing i18n messages
+     *
+     * @return the messsage parameters
+     */
+    String[] getMessageParameters();
+
+    /**
      * This method will be called before validate with a non-null ValidatorContext.
      *
-     * @param validatorContext  the validation context to use.
+     * @param validatorContext the validation context to use.
      */
     void setValidatorContext(ValidatorContext validatorContext);
 
      * The validation implementation must guarantee that setValidatorContext will
      * be called with a non-null ValidatorContext before validate is called.
      *
-     * @param object  the object to be validated.
+     * @param object the object to be validated.
      * @throws ValidationException is thrown if there is validation error(s).
      */
     void validate(Object object) throws ValidationException;
     /**
      * Sets the validator type to use (see class javadoc).
      *
-     * @param type  the type to use.
+     * @param type the type to use.
      */
     void setValidatorType(String type);
 
     /**
      * Gets the vaildator type used (see class javadoc).
      *
-     * @return  the type used
+     * @return the type used
      */
     String getValidatorType();
 

File src/java/com/opensymphony/xwork2/validator/ValidatorConfig.java

View file
  * 
  * @author James House
  * @author Rainer Hermanns
+ * @author tm_jee
+ * @author Martin Gilday 
  */
 public class ValidatorConfig extends Located {
 
     private String defaultMessage;
     private String messageKey;
     private boolean shortCircuit;
+    private String[] messageParams;
     
     /**
      * @param validatorType
     }
 
     /**
+     * @return The i18n message parameters/arguments to be used.
+     */
+    public String[] getMessageParams() {
+        return messageParams;
+    }
+
+    /**
      * Builds a ValidatorConfig
      */
     public static final class Builder {
             return this;
         }
 
+        public Builder messageParams(String[] msgParams) {
+            target.messageParams = msgParams;
+            return this;
+        }
+
         public Builder messageKey(String key) {
             if ((key != null) && (key.trim().length() > 0)) {
                 target.messageKey = key;

File src/java/com/opensymphony/xwork2/validator/validators/ValidatorSupport.java

View file
 import com.opensymphony.xwork2.validator.Validator;
 import com.opensymphony.xwork2.validator.ValidatorContext;
 
+import java.util.List;
+import java.util.ArrayList;
+
 
 /**
  * Abstract implementation of the Validator interface suitable for subclassing.
  *
  * @author Jason Carreira
+ * @author tm_jee
+ * @author Martin Gilday
  */
 public abstract class ValidatorSupport implements Validator, ShortCircuitableValidator {
 
     private boolean shortCircuit;
     private boolean parse;
     private String type;
+    private String[] messageParameters;
     private ValueStack stack;
 
 
     }
 
     public void setParse(boolean parse) {
-    	this.parse = parse;
+        this.parse = parse;
     }
 
     public boolean getParse() {
-    	return parse;
+        return parse;
     }
-    
+
     public String getMessage(Object object) {
         String message;
         boolean pop = false;
             if ((defaultMessage == null) || (defaultMessage.trim().equals(""))) {
                 defaultMessage = messageKey;
             }
-            if ( validatorContext == null) {
+            if (validatorContext == null) {
                 validatorContext = new DelegatingValidatorContext(object);
             }
-            message = validatorContext.getText(messageKey, defaultMessage);
+            List parsedMessageParameters = null;
+            if (messageParameters != null) {
+                parsedMessageParameters = new ArrayList();
+                for (int a = 0; a < messageParameters.length; a++) {
+                    if (messageParameters[a] != null) {
+                        try {
+                            Object val = stack.findValue(messageParameters[a]);
+                            parsedMessageParameters.add(val);
+                        } catch (Exception e) {
+                            // if there's an exception in parsing, we'll just treat the expression itself as the
+                            // parameter
+                            log.warn("exception while parsing message parameter [" + messageParameters[a] + "]", e);
+                            parsedMessageParameters.add(messageParameters[a]);
+                        }
+                    }
+                }
+            }
+
+            message = validatorContext.getText(messageKey, defaultMessage, parsedMessageParameters);
+
         } else {
             message = defaultMessage;
         }
         return messageKey;
     }
 
+    public String[] getMessageParameters() {
+        return this.messageParameters;
+    }
+
+    public void setMessageParameters(String[] messageParameters) {
+        this.messageParameters = messageParameters;
+    }
+
     public void setShortCircuit(boolean shortcircuit) {
         shortCircuit = shortcircuit;
     }

File src/java/xwork-validator-1.0.3.dtd

View file
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  XWork Validators DTD.
+  Used the following DOCTYPE.
+
+  <!DOCTYPE validators PUBLIC
+  		"-//OpenSymphony Group//XWork Validator 1.0.3//EN"
+  		"http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">
+-->
+
+
+<!ELEMENT validators (field|validator)+>
+
+<!ELEMENT field (field-validator+)>
+<!ATTLIST field
+	name CDATA #REQUIRED
+>
+
+<!ELEMENT field-validator (param*, message)>
+<!ATTLIST field-validator
+	type CDATA #REQUIRED
+    short-circuit (true|false) "false"
+>
+
+<!ELEMENT validator (param*, message)>
+<!ATTLIST validator
+	type CDATA #REQUIRED
+    short-circuit (true|false) "false"
+>
+
+<!ELEMENT param (#PCDATA)>
+<!ATTLIST param
+    name CDATA #REQUIRED
+>
+
+<!ELEMENT message (#PCDATA|param)*>
+<!ATTLIST message
+    key CDATA #IMPLIED
+>
+
+

File src/test/com/opensymphony/xwork2/validator/DefaultValidatorFactoryTest.java

View file
  */
 package com.opensymphony.xwork2.validator;
 
-import com.opensymphony.xwork2.XWorkTestCase;
-import com.opensymphony.xwork2.config.providers.MockConfigurationProvider;
 import com.mockobjects.dynamic.Mock;
 import com.mockobjects.dynamic.C;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Constructor;
-import java.util.Map;
-
 import junit.framework.TestCase;
 
 /**
         mockValidatorFileParser.expect("parseValidatorDefinitions", C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("myOther-validators.xml")));
         mockValidatorFileParser.expect("parseValidatorDefinitions", C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("my-validators.xml")));
         DefaultValidatorFactory factory = new DefaultValidatorFactory(null, (ValidatorFileParser) mockValidatorFileParser.proxy());
-
-        mockValidatorFileParser.verify();
     }
 }

File src/test/com/opensymphony/xwork2/validator/DefaultValidatorFileParserTest.java

View file
 
 import com.opensymphony.xwork2.util.ClassLoaderUtil;
 import com.opensymphony.xwork2.XWorkException;
-import com.opensymphony.xwork2.XWorkTestCase;
-import com.opensymphony.xwork2.ObjectFactory;
-import com.opensymphony.xwork2.validator.validators.ExpressionValidator;
-import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator;
-import com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator;
-import com.opensymphony.xwork2.validator.validators.RegexFieldValidator;
-import com.opensymphony.xwork2.config.providers.MockConfigurationProvider;
+import com.opensymphony.xwork2.validator.validators.*;
 import com.mockobjects.dynamic.Mock;
 import com.mockobjects.dynamic.C;
 
  * @author Jason Carreira
  * @author James House
  * @author tm_jee ( tm_jee (at) yahoo.co.uk )
+ * @author Martin Gilday
  */
 public class DefaultValidatorFileParserTest extends TestCase {
 
     private static final String testFileName3 = "com/opensymphony/xwork2/validator/validator-parser-test3.xml";
     private static final String testFileName4 = "com/opensymphony/xwork2/validator/validator-parser-test4.xml";
     private static final String testFileName5 = "com/opensymphony/xwork2/validator/validator-parser-test5.xml";
-    private static final String testFileName6 = "com/opensymphony/xwork2/validator/validators-fail.xml";
+    private static final String testFileName6 = "com/opensymphony/xwork2/validator/validator-parser-test6.xml";
+    private static final String testFileNameFail = "com/opensymphony/xwork2/validator/validators-fail.xml";
     private Mock mockValidatorFactory;
     private ValidatorFileParser parser;
 
     }
 
     public void testParserWithBadXML2() {
-        InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName6, this.getClass());
+        InputStream is = ClassLoaderUtil.getResourceAsStream(testFileNameFail, this.getClass());
 
         boolean pass = false;
         try {
-            parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileName6);
+            parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, testFileNameFail);
         } catch (XWorkException ex) {
             assertTrue("Wrong line number: " + ex.getLocation(), 8 == ex.getLocation().getLineNumber());
             pass = true;
         assertTrue("Validation file should have thrown exception", pass);
     }
 
+    public void testValidatorWithI18nMessage() throws Exception {
+        InputStream is = null;
+        try {
+            is = ClassLoaderUtil.getResourceAsStream(testFileName6, this.getClass());
+            mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("requiredstring")), RequiredStringValidator.class.getName());
+            mockValidatorFactory.expectAndReturn("lookupRegisteredValidatorType", C.args(C.eq("requiredstring")), RequiredStringValidator.class.getName());
+
+            List validatorConfigs = parser.parseActionValidatorConfigs((ValidatorFactory) mockValidatorFactory.proxy(), is, "-//OpenSymphony Group//XWork Validator 1.0.3//EN");
+            mockValidatorFactory.verify();
+
+            assertEquals(validatorConfigs.size(), 2);
+
+            assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getParams().get("fieldName"), "name");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getMessageParams().length, 0);
+            assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getMessageKey(), "error.name");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getDefaultMessage(), "default message 1");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getParams().size(), 1);
+            assertEquals(((ValidatorConfig)validatorConfigs.get(0)).getType(), "requiredstring");
+
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().get("fieldName"), "address");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams().length, 5);
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[0], "'tmjee'");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[1], "'phil'");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[2], "'rainer'");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[3], "'hopkins'");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageParams()[4], "'jimmy'");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getMessageKey(), "error.address");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getDefaultMessage(), "The Default Message");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().size(), 3);
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().get("trim"), "true");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getParams().get("anotherParam"), "anotherValue");
+            assertEquals(((ValidatorConfig)validatorConfigs.get(1)).getType(), "requiredstring");
+        }
+        finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+    }
 
+    
     @Override
     protected void setUp() throws Exception {
         super.setUp();

File src/test/com/opensymphony/xwork2/validator/validator-parser-test6.xml

View file
+<!DOCTYPE validators PUBLIC
+        "-//OpenSymphony Group//XWork Validator 1.0.3//EN"
+        "http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd">
+
+<validators>
+    <field name="name">
+        <field-validator type="requiredstring">
+            <message key="error.name">default message 1</message>
+        </field-validator>
+    </field>
+    <field name="address">
+        <field-validator type="requiredstring">
+            <param name="trim">true</param>
+            <param name="anotherParam">anotherValue</param>
+            <message key="error.address">  ddddd
+                <param name="1">'tmjee'</param>
+                <param name="2">'phil'</param>
+                <param name="10">'jimmy'</param>
+                <param name="6">'hopkins'</param>
+                <param name="defaultMessage">The Default Message</param>
+                <param name="someNonsenseKey">Some Nonesense Value</param>
+                <param name="3">'rainer'</param>
+            </message>
+        </field-validator>
+    </field>
+</validators>
+