Commits

tm_jee  committed 386540e

XW-590 (Add dtd for XWork validator definition)
XW-591 (Parameterizing i18n messages)

git-svn-id: http://svn.opensymphony.com/svn/xwork/branches/xwork_1-2@1687e221344d-f017-0410-9bd5-d282ab1896d7

  • Participants
  • Parent commits 8f07307
  • Branches xwork_1-2

Comments (0)

Files changed (16)

File src/java/com/opensymphony/xwork/ValidationAwareSupport.java

             errors.put(fieldName, thisFieldErrors);
         }
 
+        System.out.println("**** add "+fieldName+"->"+errorMessage);
         thisFieldErrors.add(errorMessage);
     }
 

File src/java/com/opensymphony/xwork/util/DomHelper.java

  * Helper class to create and retrieve information from location-enabled
  * DOM-trees.
  *
+ * @author Don Brown
+ * @author tmjee
  * @since 1.2
+ * @version $Date$ $Id$
  */
 public class DomHelper {
 
         }
 
         public InputSource resolveEntity(String publicId, String systemId) {
-            if (dtdMappings != null && dtdMappings.containsKey(publicId)) {
+            boolean containsDtdMappings = dtdMappings.containsKey(publicId);
+            if (dtdMappings != null && containsDtdMappings) {
                 String val = dtdMappings.get(publicId).toString();
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("using local copy of dtd with Public ID ["+publicId+"] located in ["+val+"]");
+                }
                 return new InputSource(ClassLoaderUtil.getResourceAsStream(val, DomHelper.class));
             }
             return null;

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

 
     String getMessageKey();
 
+    void setMessageParameters(String[] messageParameters);
+
+    String[] getMessageParameters();
+
     /**
      * This method will be called before validate with a non-null ValidatorContext.
      *

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

 /*
- * Copyright (c) 2002-2006 by OpenSymphony
+ * Copyright (c) 2002-2007 by OpenSymphony
  * All rights reserved.
  */
 package com.opensymphony.xwork.validator;
  * 
  * @author James House
  * @author Rainer Hermanns
+ * @author tmjee
+ * @version $Date$ $Id$
  */
 public class ValidatorConfig extends Located {
 
     private String defaultMessage;
     private String messageKey;
     private boolean shortCircuit;
+    private String[] messageParams;
     
     public ValidatorConfig() {
     }
     public void setMessageKey(String messageKey) {
         this.messageKey = messageKey;
     }
+
+
+    /**
+     * @param messageParams The i18n message parameters/arguments to be used.
+     */
+    public void setMessageParams(String[] messageParams) {
+        this.messageParams = messageParams;
+    }
+
+    /**
+     * @return The i18n message parameters/arguments to be used.
+     */
+    public String[] getMessageParams() {
+        return messageParams;
+    }
     
     /**
      * @return Returns wether the shortCircuit flag should be set on the 

File src/java/com/opensymphony/xwork/validator/ValidatorFactory.java

 
 
 /**
- * ValidatorFactory
- * 
+ * ValidatorFactory is responsible for
+ * <ul>
+ *  <li>reading in validators (eg. what validators are registired) from a
+ *      config file eg. default.xml that comes with XWork / validators.xml
+ *      / *-validators.xml that lies within the classpath</li>
+ *  <li>looking up a validator</li>
+ *  <li>registering a validator</li>
+ *  <li>obtaining a validator based on
+ *      {@link com.opensymphony.xwork.validator.ValidatorConfig}</li>
+ * </ul>
+ *
  * <p>
  * <!-- START SNIPPET: javadoc -->
  * Validation rules are handled by validators, which must be registered with 
  * <!-- END SNIPPET: exValidationRules3 -->
  * </pre>
  * 
- * @version $Date$ $Id$
  * @author Jason Carreira
  * @author James House
+ * @author tmjee
+ * @version $Date$ $Id$
  */
 public class ValidatorFactory {
 
 
         // set other configured properties
         validator.setMessageKey(cfg.getMessageKey());
+        validator.setMessageParameters(cfg.getMessageParams());
         validator.setDefaultMessage(cfg.getDefaultMessage());
         if (validator instanceof ShortCircuitableValidator) {
             ((ShortCircuitableValidator) validator).setShortCircuit(cfg.isShortCircuit());

File src/java/com/opensymphony/xwork/validator/ValidatorFileParser.java

 package com.opensymphony.xwork.validator;
 
 import com.opensymphony.xwork.ObjectFactory;
-import com.opensymphony.xwork.util.DomHelper;
 import com.opensymphony.xwork.XworkException;
+import com.opensymphony.xwork.config.providers.XmlHelper;
+import com.opensymphony.xwork.util.DomHelper;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 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.*;
 
 
 /**
  */
 public class ValidatorFileParser {
 
+    private static final Log LOG = LogFactory.getLog(ValidatorFileParser.class);
+
+
     static final String MULTI_TEXTVALUE_SEPARATOR = " ";
 
+    static final Map VALIDATOR_CONFIG_DTD_MAPPINGS = new HashMap() {
+        {
+            put("-//OpenSymphony Group//XWork Validator 1.0//EN", "xwork-validator-1.0.dtd");
+            put("-//OpenSymphony Group//XWork Validator 1.0.2//EN", "xwork-validator-1.0.2.dtd");
+            put("-//OpenSymphony Group//XWork Validator 1.0.3//EN", "xwork-validator-1.0.3.dtd");    
+        }
+    };
+
+    static final Map VALIDATOR_DEFINITION_DTD_MAPPINGS = new HashMap() {
+        {
+            put("-//OpenSymphony Group//XWork Validator Definition 1.0//EN", "xwork-validator-definition-1.0.dtd");    
+        }
+    };
+
     /**
      * Parse resource for a list of ValidatorConfig objects (configuring which validator(s) are
      * being applied to a particular field etc.)
         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");
-        
-        doc = DomHelper.parse(in, dtdMappings);
+
+        doc = DomHelper.parse(in, VALIDATOR_CONFIG_DTD_MAPPINGS);
 
         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-leve validator to be kicked off.
+            // it will not cause field-level validator to be fired.
             {NodeList validatorNodes = doc.getElementsByTagName("validator");
             addValidatorConfigs(validatorNodes, new HashMap(), validatorCfgs);}
 
         InputSource in = new InputSource(is);
         in.setSystemId(resourceName);
             
-        Document doc = DomHelper.parse(in);
+        Document doc = DomHelper.parse(in, VALIDATOR_DEFINITION_DTD_MAPPINGS);
         
         NodeList nodes = doc.getElementsByTagName("validator");
 
         }
     }
 
-    /**
-     * 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 static String getTextValue(Element valueEle) {
-        StringBuffer value = new StringBuffer();
-        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) {
-                    value.append(nodeValue.trim());
-                }
-            }
-        }
-        return value.toString().trim();
-    }
-
     private static void addValidatorConfigs(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);
-            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...
             ValidatorFactory.lookupRegisteredValidatorType(validatorType);
 
             NodeList messageNodes = validatorElement.getElementsByTagName("message");
             Element messageElement = (Element) messageNodes.item(0);
-            String key = messageElement.getAttribute("key");
 
+            final Node defaultMessageNode = messageElement.getFirstChild();
+            String defaultMessage = (defaultMessageNode == null) ? "" : defaultMessageNode.getNodeValue();
+            vCfg.setDefaultMessage(defaultMessage);
+
+            Map messageParams = XmlHelper.getParams(messageElement);
+            String key = messageElement.getAttribute("key");
             if ((key != null) && (key.trim().length() > 0)) {
                 vCfg.setMessageKey(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.setDefaultMessage((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.setMessageParams((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.setDefaultMessage(defaultMessage);
             validatorCfgs.add(vCfg);
         }
     }

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

 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 
 /**
  * Abstract implementation of the Validator interface suitable for subclassing.
  *
  * @author Jason Carreira
  * @author tmjee
+ * @version $Date$ $Id$
  */
 public abstract class ValidatorSupport implements Validator, ShortCircuitableValidator {
 
     private boolean shortCircuit;
     private boolean parse = false;
     private String type;
+    private String[] messageParameters;
 
 
     public void setParse(boolean parse) { 
             if ( validatorContext == null) {
                 validatorContext = new DelegatingValidatorContext(object);
             }
-            message = validatorContext.getText(messageKey, defaultMessage);
+
+            // The message key we have are ognl expression, let's parse it first
+            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 void setMessageParameters(String[] messageParameters) {
+        this.messageParameters = messageParameters;
+    }
+
+    public String[] getMessageParameters() {
+        return messageParameters;
+    }
+
     public void setShortCircuit(boolean shortcircuit) {
         shortCircuit = shortcircuit;
     }

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

+<?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/java/xwork-validator-definition-1.0.dtd

+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  XWork Validators Definition DTD.
+  Used the following DOCTYPE.
+
+  <!DOCTYPE validators PUBLIC
+  		"-//OpenSymphony Group//XWork Validator Definition 1.0//EN"
+  		"http://www.opensymphony.com/xwork/xwork-validator-definition-1.0.dtd">
+-->
+
+<!ELEMENT validators (validator*)>
+
+<!ELEMENT validator (#PCDATA)>
+<!ATTLIST validator
+    name CDATA #REQUIRED
+    class CDATA #REQUIRED
+>
+

File src/test/com/opensymphony/xwork/validator/ValidatorFileParserTest.java

 /*
- * Copyright (c) 2002-2003 by OpenSymphony
+ * Copyright (c) 2002-2007 by OpenSymphony
  * All rights reserved.
  */
 package com.opensymphony.xwork.validator;
 
 import com.opensymphony.util.ClassLoaderUtil;
+import com.opensymphony.xwork.XworkException;
 import com.opensymphony.xwork.config.ConfigurationManager;
 import com.opensymphony.xwork.config.providers.MockConfigurationProvider;
 import junit.framework.TestCase;
-import com.opensymphony.xwork.XworkException;
 
 import java.io.InputStream;
 import java.util.List;
 
-
 /**
  * ValidatorFileParserTest
  * <p/>
  *
  * @author Jason Carreira
  * @author James House
- * @author tm_jee ( tm_jee (at) yahoo.co.uk )
+ * @author tmjee
+ * @version $Date$ $Id$
  */
 public class ValidatorFileParserTest extends TestCase {
 
     private static final String testFileName3 = "com/opensymphony/xwork/validator/validator-parser-test3.xml";
     private static final String testFileName4 = "com/opensymphony/xwork/validator/validator-parser-test4.xml";
     private static final String testFileName5 = "com/opensymphony/xwork/validator/validator-parser-test5.xml";
+    private static final String testFileName6 = "com/opensymphony/xwork/validator/validator-parser-test6.xml";
 
     public void testParserActionLevelValidatorsShouldBeBeforeFieldLevelValidators() throws Exception {
     	InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName2, this.getClass());
         assertEquals("required", cfg.getType());
         assertEquals("foo", cfg.getParams().get("fieldName"));
         assertEquals("You must enter a value for foo.", cfg.getDefaultMessage());
-        assertEquals(4, cfg.getLocation().getLineNumber());
+        assertEquals(6, cfg.getLocation().getLineNumber());
         
         cfg = (ValidatorConfig) configs.get(3);
         assertEquals("required", cfg.getType());
         try {
             ValidatorFileParser.parseActionValidatorConfigs(is, testFileName3);
         } catch (XworkException ex) {
-            assertTrue("Wrong line number", 3==ex.getLocation().getLineNumber());
+            assertTrue("Wrong line number", 5==ex.getLocation().getLineNumber());
             pass = true;
         } 
         assertTrue("Validation file should have thrown exception", pass);
         try {
             ValidatorFileParser.parseActionValidatorConfigs(is, testFileName4);
         } catch (XworkException ex) {
-            assertTrue("Wrong line number: "+ex.getLocation(), 13==ex.getLocation().getLineNumber());
+            assertTrue("Wrong line number: "+ex.getLocation(), 15==ex.getLocation().getLineNumber());
             pass = true;
         } 
         assertTrue("Validation file should have thrown exception", pass);
         try {
             ValidatorFileParser.parseValidatorDefinitions(is, testFileName5);
         } catch (XworkException ex) {
-            assertTrue("Wrong line number", 3==ex.getLocation().getLineNumber());
+            assertTrue("Wrong line number", 6==ex.getLocation().getLineNumber());
             pass = true;
         } 
         assertTrue("Validation file should have thrown exception", pass);
     }
 
+    public void testValidatorWithI18nMessage() throws Exception {
+        InputStream is = ClassLoaderUtil.getResourceAsStream(testFileName6, this.getClass());
+        List validatorConfigs = ValidatorFileParser.parseActionValidatorConfigs(is, "-//OpenSymphony Group//XWork Validator 1.0.3//EN");
+
+        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");
+
+    }
+
 
 
     protected void setUp() throws Exception {

File src/test/com/opensymphony/xwork/validator/validator-parser-test.xml

-<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+<!DOCTYPE validators PUBLIC
+        "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
+        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
 <validators>
     <field name="foo">
         <field-validator type="required">

File src/test/com/opensymphony/xwork/validator/validator-parser-test3.xml

-<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+<!DOCTYPE validators PUBLIC
+        "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
+        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
 <validators>
     <validator type="expression" bar="die">
         <param name="expression">email.equals(email2)</param>

File src/test/com/opensymphony/xwork/validator/validator-parser-test4.xml

-<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+<!DOCTYPE validators PUBLIC
+        "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
+        "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
 
 <validators>
     <validator type="expression">

File src/test/com/opensymphony/xwork/validator/validator-parser-test5.xml

-<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+<!DOCTYPE validators PUBLIC
+  		"-//OpenSymphony Group//XWork Validator Definition 1.0//EN"
+  		"http://www.opensymphony.com/xwork/xwork-validator-definition-1.0.dtd">
+
 <validators>
     <validator name="foo" class="bar" />
 </validators>

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

+<!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>
+

File src/test/validators.xml

-<!DOCTYPE validators PUBLIC 
-  		"-//OpenSymphony Group//XWork Validator 1.0.2//EN" 
-  		"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
+<!DOCTYPE validators PUBLIC
+  		"-//OpenSymphony Group//XWork Validator Definition 1.0//EN"
+  		"http://www.opensymphony.com/xwork/xwork-validator-definition-1.0.dtd">
+
 <validators>
     <validator name="required" class="com.opensymphony.xwork.validator.validators.RequiredFieldValidator"/>
     <validator name="requiredstring" class="com.opensymphony.xwork.validator.validators.RequiredStringValidator"/>
     <validator name="stringlength" class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/>
     <validator name="regex" class="com.opensymphony.xwork.validator.validators.RegexFieldValidator"/>
 </validators>
+