Commits

unkyaku  committed 5b1deb6

Expand short-circuit mechanism to work for all validators, not just field validators.

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

  • Participants
  • Parent commits ecfca59

Comments (0)

Files changed (15)

 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4" relativePaths="false">
-  <component name="AntConfiguration" />
+  <component name="AntConfiguration">
+    <buildFile url="file://$PROJECT_DIR$/build.xml">
+      <useEmacsModeOutput value="true" />
+      <maximumHeapSize value="128" />
+      <useCustomJdk value="false" />
+      <useJavaw value="false" />
+      <includeProjectClasspath value="false" />
+      <includeParser value="true" />
+      <additionalClassPath>
+        <entry path="file://D:/bin/apache-ant-1.6.1/lib/ant-junit.jar" />
+        <entry path="file://D:/bin/apache-ant-1.6.1/lib/junit.jar" />
+        <entry path="file://$PROJECT_DIR$/lib/build/clover-1.2.3.jar" />
+      </additionalClassPath>
+    </buildFile>
+  </component>
   <component name="CodeStyleSettingsManager">
-    <option name="PER_PROJECT_SETTINGS" />
-    <option name="USE_PER_PROJECT_SETTINGS" value="false" />
+    <option name="PER_PROJECT_SETTINGS">
+      <value>
+        <option name="JAVA_INDENT_OPTIONS">
+          <value>
+            <option name="INDENT_SIZE" value="4" />
+            <option name="CONTINUATION_INDENT_SIZE" value="4" />
+            <option name="TAB_SIZE" value="4" />
+            <option name="USE_TAB_CHARACTER" value="false" />
+            <option name="SMART_TABS" value="false" />
+          </value>
+        </option>
+        <option name="JSP_INDENT_OPTIONS">
+          <value>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="4" />
+            <option name="TAB_SIZE" value="2" />
+            <option name="USE_TAB_CHARACTER" value="false" />
+            <option name="SMART_TABS" value="false" />
+          </value>
+        </option>
+        <option name="XML_INDENT_OPTIONS">
+          <value>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="4" />
+            <option name="TAB_SIZE" value="2" />
+            <option name="USE_TAB_CHARACTER" value="false" />
+            <option name="SMART_TABS" value="false" />
+          </value>
+        </option>
+        <option name="OTHER_INDENT_OPTIONS">
+          <value>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="4" />
+            <option name="TAB_SIZE" value="2" />
+            <option name="USE_TAB_CHARACTER" value="false" />
+            <option name="SMART_TABS" value="false" />
+          </value>
+        </option>
+        <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
+        <option name="BLANK_LINES_AFTER_IMPORTS" value="2" />
+        <option name="SPACE_AFTER_TYPE_CAST" value="false" />
+        <option name="FIELD_NAME_PREFIX" value="m_" />
+        <option name="STATIC_FIELD_NAME_PREFIX" value="s_" />
+        <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="8" />
+        <option name="IMPORT_LAYOUT_TABLE">
+          <value>
+            <package name="java" withSubpackages="true" />
+            <package name="javax" withSubpackages="true" />
+            <package name="gnu.trove" withSubpackages="true" />
+            <package name="org.apache" withSubpackages="true" />
+            <package name="net.sf.hibernate" withSubpackages="true" />
+            <package name="" withSubpackages="true" />
+          </value>
+        </option>
+      </value>
+    </option>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
   </component>
   <component name="CompilerConfiguration">
     <option name="DEFAULT_COMPILER" value="Javac" />
       <module fileurl="file://$PROJECT_DIR$/XWork.iml" filepath="$PROJECT_DIR$/XWork.iml" />
     </modules>
   </component>
-  <component name="ProjectRootManager" version="2" assert-keyword="false" project-jdk-name="java version &quot;1.4.2_01&quot;" />
+  <component name="ProjectRootManager" version="2" assert-keyword="false" project-jdk-name="java version &quot;1.4.2_03&quot;" />
   <component name="Regex">
     <option name="pos1" value="218" />
     <option name="pos2" value="218" />

File src/etc/xwork-validator-1.0.dtd

 <!ELEMENT validator (param*, message)>
 <!ATTLIST validator
 	type CDATA #REQUIRED
+    short-circuit (true|false) "false"
 >
 
 <!ELEMENT param (#PCDATA)>

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

 public class ActionValidatorManager {
     //~ Static fields/initializers /////////////////////////////////////////////
 
+    private static final String ACTION_VALIDATOR_KEY = "__ACTION_VALIDATOR_KEY__";
     protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
     private static final Map validatorCache = Collections.synchronizedMap(new HashMap());
     private static final Map validatorFileCache = Collections.synchronizedMap(new HashMap());
     public static void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
         List validators = getValidators(object.getClass(), context);
 
-        Set shortcircuitedFields = null;
+        Set shortcircuited = null;
 
         for (Iterator iterator = validators.iterator(); iterator.hasNext();) {
             Validator validator = (Validator) iterator.next();
                 LOG.debug("Running validator: " + validator + " for object " + object);
             }
 
+            String fieldName = ACTION_VALIDATOR_KEY;
+            FieldValidator fValidator = null;
+
             if (validator instanceof FieldValidator) {
-                FieldValidator fValidator = (FieldValidator) validator;
+                fValidator = (FieldValidator) validator;
+                fieldName = fValidator.getFieldName();
+            }
 
-                if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fValidator.getFieldName())) {
-                    continue;
+            if ((shortcircuited != null) && shortcircuited.contains(fieldName)) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Short-circuited, skipping");
                 }
 
-                if (validator instanceof ShortCircuitingFieldValidator && ((ShortCircuitingFieldValidator) validator).isShortCircuit()) {
-                    int errs = 0;
+                continue;
+            }
+
+            if (validator instanceof ShortCircuitingValidator && ((ShortCircuitingValidator) validator).isShortCircuit()) {
+                // get number of existing errors
+                int errs = 0;
 
+                if (fValidator != null) {
                     if (validatorContext.hasFieldErrors()) {
-                        Collection fieldErrors = (Collection) validatorContext.getFieldErrors().get(fValidator.getFieldName());
+                        Collection fieldErrors = (Collection) validatorContext.getFieldErrors().get(fieldName);
 
                         if (fieldErrors != null) {
                             errs = fieldErrors.size();
                         }
                     }
+                } else if (validatorContext.hasActionErrors()) {
+                    Collection actionErrors = validatorContext.getActionErrors();
 
-                    validator.validate(object);
+                    if (actionErrors != null) {
+                        errs = actionErrors.size();
+                    }
+                }
 
-                    if (validatorContext.hasFieldErrors()) {
-                        Collection fieldErrors = (Collection) validatorContext.getFieldErrors().get(fValidator.getFieldName());
+                validator.validate(object);
 
-                        if ((fieldErrors != null) && (fieldErrors.size() > errs)) {
-                            if (LOG.isDebugEnabled()) {
-                                LOG.debug("Short-circuiting");
-                            }
+                Collection errCol = null;
 
-                            if (shortcircuitedFields == null) {
-                                shortcircuitedFields = new TreeSet();
-                            }
+                if (fValidator != null) {
+                    if (validatorContext.hasFieldErrors()) {
+                        errCol = (Collection) validatorContext.getFieldErrors().get(fieldName);
+                    }
+                } else if (validatorContext.hasActionErrors()) {
+                    errCol = validatorContext.getActionErrors();
+                }
 
-                            shortcircuitedFields.add(fValidator.getFieldName());
-                        }
+                if ((errCol != null) && (errCol.size() > errs)) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Short-circuiting");
                     }
 
-                    continue;
+                    if (shortcircuited == null) {
+                        shortcircuited = new TreeSet();
+                    }
+
+                    shortcircuited.add(fieldName);
                 }
+
+                continue;
             }
 
             validator.validate(object);
     private static List buildValidators(Class clazz, String context, boolean checkFile) {
         List validators = new ArrayList();
 
+        // order matters for short-circuit
+        validators.addAll(buildAliasValidators(clazz, context, checkFile));
+        validators.addAll(buildClassValidators(clazz, checkFile));
+
         // look for validators for implemented interfaces
         Class[] interfaces = clazz.getInterfaces();
 
         Class anActionClass = clazz.getSuperclass();
 
         while (!anActionClass.equals(Object.class)) {
+            // look for validators for this class
+            validators.addAll(buildClassValidators(anActionClass, checkFile));
+
             // look for validators for implemented interfaces
             interfaces = anActionClass.getInterfaces();
 
             }
 
             // search up class hierarchy
-            validators.addAll(buildClassValidators(anActionClass, checkFile));
             anActionClass = anActionClass.getSuperclass();
         }
 
-        validators.addAll(buildClassValidators(clazz, checkFile));
-        validators.addAll(buildAliasValidators(clazz, context, checkFile));
-
         return validators;
     }
 

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

-/*
- * Copyright (c) 2002-2003 by OpenSymphony
- * All rights reserved.
- */
-package com.opensymphony.xwork.validator;
-
-
-/**
- * The ShortCircuitingFieldValidator interface defines the methods to be
- * implemented by FieldValidators that can short-circuit the validator queue
- * that it is in.
- *
- * @author Mark Woon
- */
-public interface ShortCircuitingFieldValidator extends FieldValidator {
-    //~ Methods ////////////////////////////////////////////////////////////////
-
-    /**
-     * 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();
-}

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

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork.validator;
+
+
+/**
+ * The ShortCircuitingFieldValidator interface defines the methods to be
+ * implemented by FieldValidators that can short-circuit the validator queue
+ * that it is in.
+ *
+ * @author Mark Woon
+ */
+public interface ShortCircuitingValidator extends Validator {
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    /**
+     * 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();
+}

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

 
             Validator validator = ValidatorFactory.getValidator(validatorType, params);
 
-            if (validator instanceof ShortCircuitingFieldValidator) {
-                ((ShortCircuitingFieldValidator) validator).setShortCircuit(Boolean.valueOf(validatorElement.getAttribute("short-circuit")).booleanValue());
+            if (validator instanceof ShortCircuitingValidator) {
+                ((ShortCircuitingValidator) validator).setShortCircuit(Boolean.valueOf(validatorElement.getAttribute("short-circuit")).booleanValue());
             }
 
             NodeList messageNodes = validatorElement.getElementsByTagName("message");

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

  */
 package com.opensymphony.xwork.validator.validators;
 
-import com.opensymphony.xwork.validator.ShortCircuitingFieldValidator;
+import com.opensymphony.xwork.validator.FieldValidator;
 
 
 /**
  *
  * @author Jason Carreira
  */
-public abstract class FieldValidatorSupport extends ValidatorSupport implements ShortCircuitingFieldValidator {
+public abstract class FieldValidatorSupport extends ValidatorSupport implements FieldValidator {
     //~ Instance fields ////////////////////////////////////////////////////////
 
     private String fieldName = null;
-    private boolean m_shortCircuit;
 
     //~ Methods ////////////////////////////////////////////////////////////////
 
     public String getFieldName() {
         return fieldName;
     }
-
-    public void setShortCircuit(boolean shortcircuit) {
-        m_shortCircuit = shortcircuit;
-    }
-
-    public boolean isShortCircuit() {
-        return m_shortCircuit;
-    }
 }

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

 package com.opensymphony.xwork.validator.validators;
 
 import com.opensymphony.xwork.ActionContext;
-import com.opensymphony.xwork.util.OgnlUtil;
 import com.opensymphony.xwork.util.OgnlValueStack;
 import com.opensymphony.xwork.util.TextParseUtil;
+import com.opensymphony.xwork.validator.ShortCircuitingValidator;
 import com.opensymphony.xwork.validator.ValidationException;
-import com.opensymphony.xwork.validator.Validator;
 import com.opensymphony.xwork.validator.ValidatorContext;
 
-import ognl.Ognl;
-import ognl.OgnlException;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
  * @author Jason Carreira
  * Created Feb 15, 2003 3:58:21 PM
  */
-public abstract class ValidatorSupport implements Validator {
+public abstract class ValidatorSupport implements ShortCircuitingValidator {
     //~ Instance fields ////////////////////////////////////////////////////////
 
     protected final Log log = LogFactory.getLog(this.getClass());
     protected String defaultMessage = "";
     protected String messageKey = null;
     private ValidatorContext validatorContext;
+    private boolean m_shortCircuit;
 
     //~ Methods ////////////////////////////////////////////////////////////////
 
         return messageKey;
     }
 
+    public void setShortCircuit(boolean shortcircuit) {
+        m_shortCircuit = shortcircuit;
+    }
+
+    public boolean isShortCircuit() {
+        return m_shortCircuit;
+    }
+
     public void setValidatorContext(ValidatorContext validatorContext) {
         this.validatorContext = validatorContext;
     }

File src/test/com/opensymphony/xwork/test/User-validation.xml

         <field-validator type="email" short-circuit="true">
             <message>Not a valid e-mail.</message>
         </field-validator>
-        <field-validator type="fieldexpression">
-            <param name="expression">email.endsWith('mycompany.com')</param>
-            <message>Email not from the right company.</message>
-        </field-validator>
     </field>
     <field name="email2">
         <field-validator type="required">
         <field-validator type="email">
             <message>Not a valid e-mail2.</message>
         </field-validator>
-        <field-validator type="fieldexpression">
-            <param name="expression">email.endsWith('mycompany.com')</param>
-            <message>Email2 not from the right company.</message>
-        </field-validator>
     </field>
+    <validator type="expression">
+        <param name="expression">email.equals(email2)</param>
+        <message>Email not the same as email2</message>
+    </validator>
+    <validator type="expression" short-circuit="true">
+        <param name="expression">email.startsWith('mark')</param>
+        <message>Email does not start with mark</message>
+    </validator>
 </validators>

File src/test/com/opensymphony/xwork/test/User.java

  * Copyright (c) 2002-2003 by OpenSymphony
  * All rights reserved.
  */
-/*
- * This file is copyright (c) 2001-2004, Board of Trustees of Stanford
- * University.  Created in the laboratory of Professor Russ B. Altman
- * (russ.altman@stanford.edu), Stanford University, Department of
- * Medicine, as part of the NIH PharmGKB knowledge base development
- * effort.  This work is supported by NIH U01GM61374.  Contact
- * help@pharmgkb.org for assistance, questions or suggestions.
- */
 package com.opensymphony.xwork.test;
 
 
  *
  * @author Mark Woon
  */
-public class User {
+public class User implements UserMarker {
     //~ Instance fields ////////////////////////////////////////////////////////
 
     private String m_email;

File src/test/com/opensymphony/xwork/test/UserMarker-validation.xml

+<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
+<validators>
+    <field name="email">
+        <field-validator type="fieldexpression">
+            <param name="expression">email.endsWith('mycompany.com')</param>
+            <message>Email not from the right company.</message>
+        </field-validator>
+    </field>
+    <field name="email2">
+        <field-validator type="fieldexpression">
+            <param name="expression">email.endsWith('mycompany.com')</param>
+            <message>Email2 not from the right company.</message>
+        </field-validator>
+    </field>
+    <validator type="expression">
+        <param name="expression">email2.startsWith('mark')</param>
+        <message>Email2 does not start with mark</message>
+    </validator>
+</validators>

File src/test/com/opensymphony/xwork/test/UserMarker.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.xwork.test;
+
+
+/**
+ * Marker interface to help test hierarchy traversal.
+ *
+ * @author Mark Woon
+ */
+public interface UserMarker {
+}

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

         // 2 in the class level + 2 in the alias
         assertEquals(7, validatorList.size());
 
-        final FieldValidator barValidator1 = (FieldValidator) validatorList.get(0);
+        final FieldValidator bazValidator1 = (FieldValidator) validatorList.get(0);
+        assertEquals("baz", bazValidator1.getFieldName());
+        assertTrue(bazValidator1 instanceof RequiredFieldValidator);
+
+        final FieldValidator bazValidator2 = (FieldValidator) validatorList.get(1);
+        assertEquals("baz", bazValidator2.getFieldName());
+        assertTrue(bazValidator2 instanceof IntRangeFieldValidator);
+
+        final FieldValidator barValidator1 = (FieldValidator) validatorList.get(2);
         assertEquals("bar", barValidator1.getFieldName());
         assertTrue(barValidator1 instanceof RequiredFieldValidator);
 
-        final FieldValidator barValidator2 = (FieldValidator) validatorList.get(1);
+        final FieldValidator barValidator2 = (FieldValidator) validatorList.get(3);
         assertEquals("bar", barValidator2.getFieldName());
         assertTrue(barValidator2 instanceof IntRangeFieldValidator);
 
-        final FieldValidator dateValidator = (FieldValidator) validatorList.get(2);
+        final FieldValidator dateValidator = (FieldValidator) validatorList.get(4);
         assertEquals("date", dateValidator.getFieldName());
         assertTrue(dateValidator instanceof DateRangeFieldValidator);
 
-        final FieldValidator fooValidator = (FieldValidator) validatorList.get(3);
+        final FieldValidator fooValidator = (FieldValidator) validatorList.get(5);
         assertEquals("foo", fooValidator.getFieldName());
         assertTrue(fooValidator instanceof IntRangeFieldValidator);
 
-        final Validator expressionValidator = (Validator) validatorList.get(4);
+        final Validator expressionValidator = (Validator) validatorList.get(6);
         assertTrue(expressionValidator instanceof ExpressionValidator);
-
-        final FieldValidator bazValidator1 = (FieldValidator) validatorList.get(5);
-        assertEquals("baz", bazValidator1.getFieldName());
-        assertTrue(bazValidator1 instanceof RequiredFieldValidator);
-
-        final FieldValidator bazValidator2 = (FieldValidator) validatorList.get(6);
-        assertEquals("baz", bazValidator2.getFieldName());
-        assertTrue(bazValidator2 instanceof IntRangeFieldValidator);
     }
 
     public void testGetValidatorsForInterface() {
     public void testShortCircuit() {
         // get validators
         List validatorList = ActionValidatorManager.getValidators(User.class, null);
-        assertEquals(7, validatorList.size());
+        assertEquals(10, validatorList.size());
 
         try {
             User user = new User();
             user.setName("Mark");
             user.setEmail("mark@mycompany.com");
-            user.setEmail2("mark2@mycompany.com");
+            user.setEmail2("mark@mycompany.com");
 
             // this should work
             ValidatorContext context = new GenericValidatorContext(user);
             ActionValidatorManager.validate(user, null, context);
             assertTrue(context.hasFieldErrors());
 
+            // check field errors
             List l = (List) context.getFieldErrors().get("email");
             assertNotNull(l);
             assertEquals(1, l.size());
             assertEquals(2, l.size());
             assertEquals("Not a valid e-mail2.", l.get(0));
             assertEquals("Email2 not from the right company.", l.get(1));
+
+            // check action errors
+            assertTrue(context.hasActionErrors());
+            l = (List) context.getActionErrors();
+            assertNotNull(l);
+            assertEquals(1, l.size());
+            assertEquals("Email does not start with mark", l.get(0));
         } catch (ValidationException ex) {
             ex.printStackTrace();
             fail("Validation error: " + ex.getMessage());

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

 
 import com.opensymphony.xwork.config.ConfigurationManager;
 import com.opensymphony.xwork.config.providers.MockConfigurationProvider;
+import com.opensymphony.xwork.validator.validators.ExpressionValidator;
 import com.opensymphony.xwork.validator.validators.IntRangeFieldValidator;
 import com.opensymphony.xwork.validator.validators.RequiredFieldValidator;
 
 
         List configs = ValidatorFileParser.parseActionValidators(is);
         assertNotNull(configs);
-        assertEquals(3, configs.size());
+        assertEquals(5, configs.size());
 
         Validator validator = (Validator) configs.get(0);
         assertTrue(validator instanceof RequiredFieldValidator);
         assertEquals("You must enter a value for foo.", fieldValidator.getDefaultMessage());
 
         validator = (Validator) configs.get(1);
-        assertTrue(validator instanceof ShortCircuitingFieldValidator);
-        assertTrue(((ShortCircuitingFieldValidator) validator).isShortCircuit());
+        assertTrue(validator instanceof ShortCircuitingValidator);
+        assertTrue(((ShortCircuitingValidator) validator).isShortCircuit());
 
         validator = (Validator) configs.get(2);
         assertTrue(validator instanceof IntRangeFieldValidator);
-        assertFalse(((ShortCircuitingFieldValidator) validator).isShortCircuit());
+        assertFalse(((ShortCircuitingValidator) validator).isShortCircuit());
+
+        validator = (Validator) configs.get(3);
+        assertTrue(validator instanceof ExpressionValidator);
+        assertFalse(((ShortCircuitingValidator) validator).isShortCircuit());
+
+        validator = (Validator) configs.get(4);
+        assertTrue(validator instanceof ExpressionValidator);
+        assertTrue(((ShortCircuitingValidator) validator).isShortCircuit());
     }
 
     protected void setUp() throws Exception {

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

             <message>bar must be between ${min} and ${max}, current value is ${bar}.</message>
         </field-validator>
     </field>
+    <validator type="expression">
+        <param name="expression">email.equals(email2)</param>
+        <message>Email not the same as email2</message>
+    </validator>
+    <validator type="expression" short-circuit="true">
+        <param name="expression">email.startsWith('mark')</param>
+        <message>Email does not start with mark</message>
+    </validator>
 </validators>