Search code examples
jsf-2richfaces

rich:calendar native validation hides/overwrites/?? my bean validation errors


My application uses rich:calendars and h:inputText. If I put input bad data in the UI for the intputText (i.e. non-numeric data) and press "Search" I get the proper error message from the bean. However, if I put in invalid data in both the inputText (non-numeric) and the rich:calendar(non-date related input such as "foo"), I only get the rich:calendar error messages back. It is as if the native rich calendar validation messages knocks out the bean validation messages.

How do I get all messages to display?

The xhtml code is as follows:

    <html xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:a4j="http://richfaces.org/a4j"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:tr="http://myfaces.apache.org/trinidad"
      xmlns:rich="http://richfaces.org/rich">
<f:view>
    <br/>
    <div class="container">
    <tr:form id="searchCriteria" defaultCommand="appealSearchManager.search">
        <a4j:outputPanel id="errorMessagesPanel">
            <h:messages id="errorMessages"/>
        </a4j:outputPanel>
        <div class="div30">
            <p><h:outputText value="#{messages.ProgramInvoiceId}"/></p>
            <h:inputText id="programInvoiceId" value="#{appealSearchManager.programInvoiceId}"/>
        </div>
        <div class="div30">
        <p><h:outputText value="#{messages.ResponseReleaseDate}"/></p>
        <rich:calendar id="responseReleaseDateBegin"
            enableManualInput="true"  datePattern="MM/dd/yyyy"
            buttonIcon="/images/calendar_icon.jpg" buttonClass="calendar"
            converterMessage="Invalid Response Release begin date.  Format must be blah, blah, blah."
            value="#{appealSearchManager.responseReleaseDateBegin}">
        </rich:calendar>
        <rich:calendar id="responseReleaseDateEnd"
            enableManualInput="true"  datePattern="MM/dd/yyyy"
            buttonIcon="/images/calendar_icon.jpg" buttonClass="calendar"
           converterMessage="Invalid Response Release End date.  Format must be blah, blah, blah."
            value="#{appealSearchManager.responseReleaseDateEnd}">
        </rich:calendar>
        </div>

        <div class="searchaction">
            <div>
                <ul>

                    <li>
                        <a4j:commandLink id="searchButton"
                                         value="#{messages.Search}"
                                         actionListener="#{appealSearchManager.search}"
                                         reRender="errorMessagesPanel, richErrorMessages, errorMessages"
                                         styleClass="searchbtn"/>
                    </li>
                </ul>
            </div>
        </div>
    </tr:form>
    </div>
</f:view>
</html>

and the bean code:

    public void search(ActionEvent e) {
    setHasErrors(validateCriteria());
}

private boolean validateCriteria() {
    boolean isValid = true;
    //always check for Program Invoice ID may contain a comma-separated list of ids
    if (getProgramInvoiceId() != null && !getProgramInvoiceId().equals("") && !areValidProgamInvoiceIds(getProgramInvoiceId())) {
        String errorMessage = "Invalid Program Invoice ID.  Must be comma delimited list of numbers.";
        FacesContext.getCurrentInstance().addMessage(null,
                                                     new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
        isValid = false;
    }
    //check Dates Response Release Date
    if (!isValidDateRange("Response Release Date", getResponseReleaseDateBegin(), getResponseReleaseDateEnd())) {
        isValid = false;
    }
    return isValid;
}

private boolean areValidProgamInvoiceIds(String ids) {
    boolean validIds = true;
    String regexp = "^([0-9]+(-[0-9]+)*,*)+$";
    Pattern pattern = Pattern.compile(regexp);
    if (!pattern.matcher(ids).matches()) {
        validIds = false;
    }
    return validIds;
}

private boolean isValidDateRange(String dateRange, Date startDate, Date endDate) {
    boolean isValidDateAndRange = true;
    Calendar futureDate = Calendar.getInstance();
    futureDate.roll(Calendar.DAY_OF_MONTH, 2);
    if ((startDate != null && (endDate == null || endDate.equals(""))) ||
         ((startDate == null || startDate.equals("")) && endDate != null)) {
        //one date is null and the other has a value.  We need both values to do a search.
        String errorMessage = "Invalid " + dateRange + " range.  Missing Date. A date range must contain both start and end dates.";
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
    } else if (startDate != null && endDate != null) {
        if (isValidDateAndRange) {
            //future start dates are not allowed
            if (startDate.after(futureDate.getTime())) {
                String errorMessage = "Invalid date range.  Start date must be less than today's date.";
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
                isValidDateAndRange = false;
            }
            //start date greater than the end date is not allowed
            if (endDate.before(startDate)) {
                String errorMessage = "Invalid date range.  End date must be greater than or equal to start date.";
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, errorMessage));
                isValidDateAndRange = false;
            }
        }
    }
    return isValidDateAndRange;
}

Any suggestions would be greatly appreciated. Thanks!


Solution

  • You should not perform validation inside action method. You should perform it using a normal Validator which you bind by <f:validator> or any of the standard <f:validateXxx> tags. The action method is never invoked when validation fails.

    For example, to validate the program invoice ID, use <f:validateRegex>.

    <h:inputText id="programInvoiceId" value="#{appealSearchManager.programInvoiceId}" validatorMessage="Invalid Program Invoice ID.  Must be comma delimited list of numbers.">
        <f:validateRegex pattern="^([0-9]+(-[0-9]+)*,*)+$" />
    </h:inputText>
    

    Validating the date range is a bit more complex. To the point, you need to put the Validator class as <f:validator> on one of the components and pass the value of the other component along as an attribute. For an example, see also Compare two fields that use same class.

    By the way, be careful with terminology, you are not using "bean validation" at all, but you are just manually validating in the action method.