eclipse-ee4j / mojarra

Mojarra, a Jakarta Faces implementation
Other
165 stars 112 forks source link

class-level bean validation: Array data not copied after bean cloning #5450

Closed thomaspries closed 6 months ago

thomaspries commented 6 months ago

Describe the bug

After cloning the bean, all scalar values are copied into the new bean for validation, but modified data in array stay at initial value altough the input values are converted an validated correctly.

To Reproduce

Steps to reproduce the behavior:

  1. Follow one of the ubiquitous password-examples for WholeBeanValidation in JSF (e.g. this one)
  2. make simple Datatype
    class TestData{ 
    int a; 
    int b; 
    ... }
  3. add
    TestData[] data;

    to Backingbean and initialise the array with some data (to verify, that conversion and validation of TestData works, add scalar TestData x1;)

  4. I'm uncertain, if i have to allocate a new Array in the clone method by hand or not, tried both, same effect
  5. add Converter and Validator for TestData-type
  6. gather data in facelet via
    <ui:repeat value="#{testController.data}" varStatus="status" var="d">
    <h:inputText id="valInput_#{status.index}" value="#{testController.data[status.index]}">
     <f:validator validatorId="testDataFacesValidator"/>
     <f:validateBean validationGroups="ValidationGroup"/>
    </h:inputText>
    </ui:repeat>
  7. Validation phase:

    public class PasswordValidator implements ConstraintValidator<Password, PasswordHolder> {
    
    @Override
    public void initialize(Password constraintAnnotation) { }
    
    @Override
    public boolean isValid(PasswordHolder value, ConstraintValidatorContext context) {
    boolean result;
    
    // both password strings are visible with new data => ok
    // scalar TestData is visible with new data => ok
    // array still contains initial values => not ok
    
    return result;
    }
    }

Expected behavior

I would like to see altered data in the array to validate them together with the other data.

Desktop

BalusC commented 6 months ago

This is a known limitation. Nested properties aren't supported by <f:validateWholeBean>. It only supports the bean's own "flat" properties.

Consider using OmniFaces <o:validateBean> instead. It not only supports nested properties but it will also greatly reduce the <f:validateBean> and clone() boilerplate. Detecting nested properties and associating validation errors with them is actually quite complex and a lot of work has been done in crystallizing the <o:validateBean> on this.

In order to use <o:validateBean>:

  1. Remove all <f:validateBean> and <f:validateWholeBean> tags.
  2. Remove Cloneable interface and clone() method from backing bean.
  3. Put @jakarta.validation.Valid annotation on private TestData[] data; property of backing bean.
  4. In place where the <f:validateWholeBean> tag was originally located in XHTML, add the <o:validateBean> with exactly the same attributes:
<o:validateBean value="#{testController}" validationGroups="ValidationGroup" />

Note that you can further simplify your <ui:repeat> on data as follows as well:

<ui:repeat value="#{testController.data}" var="d">
  <h:inputText id="valInput" value="#{d.a}"> <!-- and another one for #{d.b}? -->
    <f:validator validatorId="testDataFacesValidator"/>
  </h:inputText>
</ui:repeat>

Also note that using EL in ID attribute of <h:inputText> within <ui:repeat> has no effect. It has only effect when you use <c:forEach> instead but that's not necessary. See also https://stackoverflow.com/q/3342984

thomaspries commented 6 months ago

Thank's for your response (I somehow missed that fact in the documentation), OmniFaces looks promising, I will use that.

... I implemented the steps mentions above and it works perfectly. :-))