Struts2 validating 3 times on single textfield

2k views Asked by At

I am having really upsetting issue with Struts(2.2.3). Here is my field validations on ActionName-validation.xml

<field name="txtRequestDateFrom">
   <field-validator type="conversion">
      <param name="repopulateField">false</param>
      <message>${getText("E011", {"Date from"})}</message>
   </field-validator>
</field>

I don't have validate() method in my action class. And I have this in my action class:

private Date txtRequestDateFrom;
{getter, setters}

When I enter letters on my txtRequestDateFrom field I get 3 validation messages on

<s:fielderror fieldName="txtRequestDateFrom"/> 

It look like this

Invalid field value for field "txtRequestDateFrom".
Invalid field value for field "txtRequestDateFrom".
Date from has an invalid value

I have my custom theme, and I am sure there is not any much modification from SIMPLE theme. My interceptor stack is pretty much as same default value stack.

<interceptor-stack name="defaultStack">
        <interceptor-ref name="security"/>
            <interceptor-ref name="exception"/>
            <interceptor-ref name="alias"/>
            <interceptor-ref name="servletConfig"/>
            <interceptor-ref name="i18n"/>
            <interceptor-ref name="prepare"/>
            <interceptor-ref name="chain"/>
            <interceptor-ref name="debugging"/>
            <interceptor-ref name="scopedModelDriven"/>
            <interceptor-ref name="modelDriven"/>
            <interceptor-ref name="fileUploadStack" />
            <interceptor-ref name="fileUpload" >
            <param name="maximumSize">4000000</param>
        </interceptor-ref> 
            <interceptor-ref name="checkbox"/>
            <interceptor-ref name="multiselect"/>
            <interceptor-ref name="staticParams"/>
            <interceptor-ref name="actionMappingParams"/>                   
        <interceptor-ref name="params"/>
        <interceptor-ref name="conversionError" />
        <interceptor-ref name="validation">
            <param name="excludeMethods">execute, complete ...</param>
        </interceptor-ref>
        <interceptor-ref name="workflow"/>

    </interceptor-stack>

I found out that one field error can be removed by removing conversionError interceptor from the stack. But I don't think that would cause this problem. Struts should be able to show errors only defined by developer, right?

Please help me on this

3

There are 3 answers

0
batbaatar On BEST ANSWER

I found that my custom DateTimeConverter was causing the exceptions and the extra error message. Because I found the code below from Struts2 book in order to change my Date's normal format. When it throws an exception, it shows the exception on console and error message on field error rather than passing the exception to the validator. I think it is sort of bug because this class extends StrutsTypeConverter and it should work as normal converters.

public class StringToDateTimeConverter extends StrutsTypeConverter {

private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd");

public Object convertFromString(Map context, String[] strings, Class toClass) {     
    if (strings == null || strings.length == 0 || strings[0].trim().length() == 0) {
        return null;
    }

    try {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(DATETIME_FORMAT.parse(strings[0]));
        calendar.set(Calendar.HOUR, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        return calendar.getTime();

    } catch (ParseException e) {
        throw new TypeConversionException(e);
    }
}

public String convertToString(Map context, Object date) {
    if (date != null && date instanceof Date) {
        return DATETIME_FORMAT.format(date);
    } else {
        return null;
    }
}

}

Anyway I changed throw new TypeConversionException(e); to return null; and added REQUIRED validator on validation XML. Now it shows me error when I put invalid date on my date fields.

PS: Is there any other way to change Struts global date format? Thanks

5
Asa On

You need to understand how Struts2 handles conversion errors.

Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string, "", cannot be converted to a number might not be important - especially in a web environment where it is hard to distinguish between a user not entering a value vs. entering a blank value.

...

It is important to know that none of these errors are actually reported directly. Rather, they are added to a map called conversionErrors in the ActionContext. There are several ways this map can then be accessed and the errors can be reported accordingly.

There are two ways the error reporting can occur:

  1. Globally, using the Conversion Error Interceptor
  2. On a per-field basis, using the conversion validator

You are using both mechanisms, thus duplicating the errors found. As the documentation states, usually you don't want to report all conversion errors, and thus should remove the ConversionErrorInterceptor from the stack. Now you can selectively raise conversion errors as field errors using the conversion validator.

0
GreenTurtle On

I faced a similar problem yesterday and finally found a solution which I like to share. I'm using annotations in my actions for validation, so I changed default struts interceptor stack and put my SensibleConversionErrorInterceptor instead of StrutsConversionErrorInterceptor in. This one is total identically but doesn't create any validation errors. Instead they are generated by validation configured in annotations in my actions.

Here is my converter:

public class SensibleConversionErrorInterceptor extends StrutsConversionErrorInterceptor {

   private static final long serialVersionUID = 8186282792289268544L;

   @Override
   public String intercept(ActionInvocation invocation) throws Exception {

      ActionContext invocationContext = invocation.getInvocationContext();
      Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
      ValueStack stack = invocationContext.getValueStack();

      HashMap<Object, Object> fakie = null;

      for (Map.Entry<String, Object> entry : conversionErrors.entrySet()) {
         String propertyName = entry.getKey();
         Object value = entry.getValue();

         if (shouldAddError(propertyName, value)) {
            // removed cause error messages are generated from annotations in actions
            // String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
            // Object action = invocation.getAction();
            // if (action instanceof ValidationAware) {
            //    ValidationAware va = (ValidationAware) action;
            //    va.addFieldError(propertyName, message);
            // }

            if (fakie == null) {
               fakie = new HashMap<Object, Object>();
            }

            fakie.put(propertyName, getOverrideExpr(invocation, value));
         }
      }

      if (fakie != null) {
         // if there were some errors, put the original (fake) values in place right before the result
         stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
         invocation.addPreResultListener(new PreResultListener() {
            public void beforeResult(ActionInvocation invocation, String resultCode) {
               Map<Object, Object> fakie = (Map<Object, Object>) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
               if (fakie != null) {
                  invocation.getStack().setExprOverrides(fakie);
               }
            }
        });
     }
     return invocation.invoke();
  }

}

And an example action:

@Conversion
public class ProductAction extends ActionSupport {

   private Product product;
   // getter, setter and so on...

   @Action(...)
   @Validations(
       requiredFields = {
           @RequiredFieldValidator(
               type = ValidatorType.FIELD,
               fieldName = "product.validFrom",
               message = "required.product.validFrom",
               shortCircuit = true
           )
       },
       conversionErrorFields = {
          @ConversionErrorFieldValidator(
              fieldName = "product.validFrom",
              key = "invalid.fieldvalue.product.validFrom'",
              shortCircuit = true
          ) 
       }
   )
   public String saveOrUpdate() {
      // do something here...
   }
}