MyFaces Core
  1. MyFaces Core
  2. MYFACES-3547

Can't use expression for validator attributes

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Minor Minor
    • Resolution: Unresolved
    • Affects Version/s: 2.1.7
    • Fix Version/s: None
    • Component/s: General
    • Labels:
      None
    • Environment:
      validator ajax

      Description

      Attaching a test case that shows a problem when using an expression for an attribute in a validator. We have markup that looks like this:

      <h:inputText id="ajaxMy"
      value="#

      {testBean.myNumber}

      ">
      <f:validateLongRange minimum="1"
      maximum="#

      {testBean.maxValue}

      "/>
      <f:ajax execute="@this"
      render="@form"/>
      </h:inputText>

      When the value of the maximum attribute is modified via Ajax from another input field, the value of the bean is properly set but the validator doesn't resolve appear to resolve the expression at the right time and the result is that validation occurs against the "old" values.

      1. validator.zip
        9 kB
        Deryk Sinotte

        Activity

        Hide
        Mike Kienenberger added a comment - - edited

        To answer my own question, the key is to create a custom Facelets validator MetaRule which detects when an attribute value is non-literal. In that case, the rule will call a custom method to inject the ValueExpression rather than setting the attribute value directly. The validator or converter is responsible for storing the ValueExpression in state and then evaluating it when needed.

        MyFaces Commons provides a framework for making all of this happen with minimal effort. However, someone could simply create their own MetaRule based on the _ValidationRule in MyFaces Commons, and do this themselves.

        http://svn.apache.org/viewvc/myfaces/commons/trunk/myfaces-commons-validators/src/main/java/org/apache/myfaces/commons/validator/_ValidatorRule.java?view=markup

        This can be done for any version of JSF, although the details in the Facelets handler changed starting in JSF 1.2.

        Show
        Mike Kienenberger added a comment - - edited To answer my own question, the key is to create a custom Facelets validator MetaRule which detects when an attribute value is non-literal. In that case, the rule will call a custom method to inject the ValueExpression rather than setting the attribute value directly. The validator or converter is responsible for storing the ValueExpression in state and then evaluating it when needed. MyFaces Commons provides a framework for making all of this happen with minimal effort. However, someone could simply create their own MetaRule based on the _ValidationRule in MyFaces Commons, and do this themselves. http://svn.apache.org/viewvc/myfaces/commons/trunk/myfaces-commons-validators/src/main/java/org/apache/myfaces/commons/validator/_ValidatorRule.java?view=markup This can be done for any version of JSF, although the details in the Facelets handler changed starting in JSF 1.2.
        Hide
        Mike Kienenberger added a comment - - edited

        When I use myfaces-validators12-1.0.2.jar with myfacesi-1.2.9, I do not see the re-evaluation behavior that you describe. Is this only valid for JSF 2.0? Or does it depend on a specific optional jar file?

        I do not use annotations – is the use of annotations required to make this happen?

        I modified my class to extend org.apache.myfaces.commons.validator.ValidatorBase. Previously it extended org.apache.myfaces.validator.ValidatorBase, which to my quick glance used the same pattern.

        I copied amex code out of the credit card validator in commons just to be sure and renamed it for my own method, also implementing the stateholder code.

        Maybe there's something further I overlooked, but both the new ValidatorBase code and the old ValidatorBase code both suffer from the same issue. The setNoChangeValue() method is only called once from buildView. And because of that, getValueExpression("noChangeValue") is never called since the "isSet" flag is always true after that point.

        SocialSecurityNumberValidator.setNoChangeValue(String) line: 111
        NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
        NativeMethodAccessorImpl.invoke(Object, Object[]) line: 57
        DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
        Method.invoke(Object, Object...) line: 616
        BeanPropertyTagRule$DynamicPropertyMetadata.applyMetadata(FaceletContext, Object) line: 75
        MetadataImpl.applyMetadata(FaceletContext, Object) line: 36
        ValidateHandler(MetaTagHandler).setAttributes(FaceletContext, Object) line: 62
        ValidateHandler.apply(FaceletContext, UIComponent) line: 98
        [...]
        FaceletViewHandler.buildView(FacesContext, UIViewRoot) line: 524
        FaceletViewHandler.renderView(FacesContext, UIViewRoot) line: 567

        public String getNoChangeValue()
        {
        if (noChangeValueSet)

        { return noChangeValueValue; }

        ValueExpression vb = getValueExpression("noChangeValue");
        if (vb != null)

        { return (String) vb.getValue(getFacesContext().getELContext()); }

        return null;
        }

        public void setNoChangeValue(String noChangeValue)

        { this.noChangeValue = noChangeValue; this.noChangeValueSet = true; }
        Show
        Mike Kienenberger added a comment - - edited When I use myfaces-validators12-1.0.2.jar with myfacesi-1.2.9, I do not see the re-evaluation behavior that you describe. Is this only valid for JSF 2.0? Or does it depend on a specific optional jar file? I do not use annotations – is the use of annotations required to make this happen? I modified my class to extend org.apache.myfaces.commons.validator.ValidatorBase. Previously it extended org.apache.myfaces.validator.ValidatorBase, which to my quick glance used the same pattern. I copied amex code out of the credit card validator in commons just to be sure and renamed it for my own method, also implementing the stateholder code. Maybe there's something further I overlooked, but both the new ValidatorBase code and the old ValidatorBase code both suffer from the same issue. The setNoChangeValue() method is only called once from buildView. And because of that, getValueExpression("noChangeValue") is never called since the "isSet" flag is always true after that point. SocialSecurityNumberValidator.setNoChangeValue(String) line: 111 NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method] NativeMethodAccessorImpl.invoke(Object, Object[]) line: 57 DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43 Method.invoke(Object, Object...) line: 616 BeanPropertyTagRule$DynamicPropertyMetadata.applyMetadata(FaceletContext, Object) line: 75 MetadataImpl.applyMetadata(FaceletContext, Object) line: 36 ValidateHandler(MetaTagHandler).setAttributes(FaceletContext, Object) line: 62 ValidateHandler.apply(FaceletContext, UIComponent) line: 98 [...] FaceletViewHandler.buildView(FacesContext, UIViewRoot) line: 524 FaceletViewHandler.renderView(FacesContext, UIViewRoot) line: 567 public String getNoChangeValue() { if (noChangeValueSet) { return noChangeValueValue; } ValueExpression vb = getValueExpression("noChangeValue"); if (vb != null) { return (String) vb.getValue(getFacesContext().getELContext()); } return null; } public void setNoChangeValue(String noChangeValue) { this.noChangeValue = noChangeValue; this.noChangeValueSet = true; }
        Hide
        Leonardo Uribe added a comment - - edited

        All validators extending from:

        http://svn.apache.org/repos/asf/myfaces/commons/trunk/myfaces-commons-validators/src/main/java/org/apache/myfaces/commons/validator/ValidatorBase.java

        do not evaluate params on build view time, instead they are evaluated when validation is performed.

        Look this example:

        http://svn.apache.org/repos/asf/myfaces/commons/trunk/examples/myfaces-commons-facelets-examples/src/main/webapp/creditcardvalidator.xhtml

        <h:form id="myform">
        <p>Some valid test numbers</p>

        <ul>
        <li>Mastercard : 5555555555554444</li>
        <li>Mastercard : 5105105105105100</li>
        <li>Visa : 4111111111111111</li>
        <li>Visa : 4111111111111111</li>
        <li>Discover : 6011111111111117</li>
        <li>Discover : 6011000990139424</li>
        <li>American Express : 378282246310005</li>
        <li>American Express : 371449635398431</li>
        </ul>

        <h:panelGrid columns ="3">
        <h:outputLabel for="creditCardType" value="#

        {example_messages['credit_type']}

        " />
        <h:selectOneRadio id="creditCardType" value="#

        {validateCreditCard.creditCardType}

        " immediate="true">
        <f:selectItems value="#

        {validateCreditCard.creditCardTypes}

        "/>
        </h:selectOneRadio>
        <h:message id="creditCardTypeError" for="creditCardType" styleClass="error" />

        <h:outputLabel for="creditCardNumber" value="#

        {example_messages['credit_number']}

        " />
        <h:inputText id="creditCardNumber" value="#

        {validateCreditCard.creditCardNumber}

        " required="true">
        <mcv:validateCreditCard
        amex="#

        {mcc:findComponent('myform:creditCardType').value == 'AMEX'}

        "
        visa="#

        {mcc:findComponent('myform:creditCardType').value == 'VISA'}

        "
        discover="#

        {mcc:findComponent('myform:creditCardType').value == 'DISCOVER'}

        "
        mastercard="#

        {mcc:findComponent('myform:creditCardType').value == 'MASTERCARD'}

        " />
        </h:inputText>
        <h:message id="creditCardNumberError" for="creditCardNumber" styleClass="error" />
        </h:panelGrid>
        <h:panelGroup/>
        <h:commandButton id="validateButton" value="#

        {example_messages['button_submit']}

        " action="#

        {validateCreditCard.submit}

        "/>
        <h:panelGroup/>
        </h:form>

        The type has immediate="true", which ensures it is evaluated and set into the bean before the number, and in that way when the validation occur, the expression that refers to the type are evaluated first and taken into account. The same principle should work in ajax case.

        Show
        Leonardo Uribe added a comment - - edited All validators extending from: http://svn.apache.org/repos/asf/myfaces/commons/trunk/myfaces-commons-validators/src/main/java/org/apache/myfaces/commons/validator/ValidatorBase.java do not evaluate params on build view time, instead they are evaluated when validation is performed. Look this example: http://svn.apache.org/repos/asf/myfaces/commons/trunk/examples/myfaces-commons-facelets-examples/src/main/webapp/creditcardvalidator.xhtml <h:form id="myform"> <p>Some valid test numbers</p> <ul> <li>Mastercard : 5555555555554444</li> <li>Mastercard : 5105105105105100</li> <li>Visa : 4111111111111111</li> <li>Visa : 4111111111111111</li> <li>Discover : 6011111111111117</li> <li>Discover : 6011000990139424</li> <li>American Express : 378282246310005</li> <li>American Express : 371449635398431</li> </ul> <h:panelGrid columns ="3"> <h:outputLabel for="creditCardType" value="# {example_messages['credit_type']} " /> <h:selectOneRadio id="creditCardType" value="# {validateCreditCard.creditCardType} " immediate="true"> <f:selectItems value="# {validateCreditCard.creditCardTypes} "/> </h:selectOneRadio> <h:message id="creditCardTypeError" for="creditCardType" styleClass="error" /> <h:outputLabel for="creditCardNumber" value="# {example_messages['credit_number']} " /> <h:inputText id="creditCardNumber" value="# {validateCreditCard.creditCardNumber} " required="true"> <mcv:validateCreditCard amex="# {mcc:findComponent('myform:creditCardType').value == 'AMEX'} " visa="# {mcc:findComponent('myform:creditCardType').value == 'VISA'} " discover="# {mcc:findComponent('myform:creditCardType').value == 'DISCOVER'} " mastercard="# {mcc:findComponent('myform:creditCardType').value == 'MASTERCARD'} " /> </h:inputText> <h:message id="creditCardNumberError" for="creditCardNumber" styleClass="error" /> </h:panelGrid> <h:panelGroup/> <h:commandButton id="validateButton" value="# {example_messages['button_submit']} " action="# {validateCreditCard.submit} "/> <h:panelGroup/> </h:form> The type has immediate="true", which ensures it is evaluated and set into the bean before the number, and in that way when the validation occur, the expression that refers to the type are evaluated first and taken into account. The same principle should work in ajax case.
        Hide
        Mike Kienenberger added a comment -

        Leonardo,

        Are you sure that the attribute pattern used in DateTimeConverter will allow a validator to evaluate the attribute expression at render-time? I had been using this very pattern for years, but found it ineffective for anything other than build-time evaluation two days ago.

        Show
        Mike Kienenberger added a comment - Leonardo, Are you sure that the attribute pattern used in DateTimeConverter will allow a validator to evaluate the attribute expression at render-time? I had been using this very pattern for years, but found it ineffective for anything other than build-time evaluation two days ago.
        Hide
        Mike Kienenberger added a comment -

        I unfortunately hit the same issue a couple of days ago. The real problem is that validators appear to evaluate the attributes only at page build time, and then never again.

        Here is a link describing what I ended up doing to solve the problem:

        http://stackoverflow.com/questions/4439850/refresh-jsf-validator-attributes-on-rerender

        I hadn't realized that the MyFaces Commons project had a possible solution for this, and I'll have to take a look at what they did, as I'm not completely happy with my own approach.

        Maybe someone should open a JSF spec bug on this – it makes little sense to me that these things are only evaluated at build-time for a page.

        Show
        Mike Kienenberger added a comment - I unfortunately hit the same issue a couple of days ago. The real problem is that validators appear to evaluate the attributes only at page build time, and then never again. Here is a link describing what I ended up doing to solve the problem: http://stackoverflow.com/questions/4439850/refresh-jsf-validator-attributes-on-rerender I hadn't realized that the MyFaces Commons project had a possible solution for this, and I'll have to take a look at what they did, as I'm not completely happy with my own approach. Maybe someone should open a JSF spec bug on this – it makes little sense to me that these things are only evaluated at build-time for a page.
        Hide
        Leonardo Uribe added a comment -

        It is a known weakness in jsf standard converters and validator. To overcome this limitation, some converters and validators were created in MyFaces Commons project that evaluates its parameters when the value is checked, There is no variant for f:validateLongRange, but there is a mcc:convertNumber and mcc:convertDateTime.

        Unfortunately, by compatibility with the spec it is not possible to change the current behavior. A change should be done at spec level. It is possible to write a custom implementation of that validator using myfaces builder plugin.

        Show
        Leonardo Uribe added a comment - It is a known weakness in jsf standard converters and validator. To overcome this limitation, some converters and validators were created in MyFaces Commons project that evaluates its parameters when the value is checked, There is no variant for f:validateLongRange, but there is a mcc:convertNumber and mcc:convertDateTime. Unfortunately, by compatibility with the spec it is not possible to change the current behavior. A change should be done at spec level. It is possible to write a custom implementation of that validator using myfaces builder plugin.
        Hide
        Deryk Sinotte added a comment -

        The test case can be built using:

        mvn -Djsfimpl="myfaces" clean package. The properly jsfimpl can also be set to "mojarra" as it exhibits the same problem.

        Show
        Deryk Sinotte added a comment - The test case can be built using: mvn -Djsfimpl="myfaces" clean package. The properly jsfimpl can also be set to "mojarra" as it exhibits the same problem.

          People

          • Assignee:
            Unassigned
            Reporter:
            Deryk Sinotte
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:

              Development