I'm using WAS 8.0.0.5's MyFaces 2.0.4 AND CDI (can't replace WAS 8's version of JSF 2.0 without losing CDI).
I have 2 custom validators registered to a component. The first one does a multi-field comparison. The second validator uses CDI to inject a SSB, which uses an entity manager to call a database.
If I enter information that causes the 1st validator to intentionally fail, the 2nd validator is still executed. Why? How can I avoid the 2nd validation if the first validation fails? I thought that if one validator failed, then all subsequent validations are bypassed.
<h:form id="registrationForm">
<fieldset>
<p:messages id="messages" showDetail="true" autoUpdate="true" closable="true" />
<legend>Register</legend>
<div class="form-row">
<h:outputLabel for="userId" value="*User Id"/>
<h:inputText id="userId" value="#{registration.userId}" required="true" size="20">
<f:validator validatorId="userIdPasswordValidator" />
<f:attribute name="passwordComponent" value="#{passwordComponent}"/>
<f:validator binding="#{duplicateUserValidator}" /> <-- uses CDI
</h:inputText>
</div>
<div class="form-row">
<h:outputLabel for="password" value="*Password"/>
<h:inputSecret id="password" type="password" binding="#{passwordComponent}" value="#{registration.password}" required="true">
</h:inputSecret>
</div>
<div class="form-row">
<h:outputLabel for="confirmPassword" value="*Confirm Password"/>
<h:inputSecret id="confirmPassword" type="password" value="#{registration.confirmPassword}" required="true" />
</div>
<div class="form-row">
<h:commandButton styleClass="btn btn-warning" value="Register" type="submit" action="#{registration.register}" />
</div>
</fieldset>
</h:form>
1st validator:
@FacesValidator("userIdPasswordValidator")
public class UserIdPasswordValidator implements Validator {
@Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
String userId = (String)value;
UIInput passwordInput = (UIInput)component.getAttributes().get("passwordComponent");
String password = (String) passwordInput.getSubmittedValue();
if (userId.equals(password)) {
FacesMessage message = new FacesMessage(null, "The Password cannot be the same as your User ID.");
throw new ValidatorException(message);
}
}
}
2nd validator:
@Named
@RequestScoped
public class DuplicateUserValidator implements Validator {
@Inject
UserService userService;
@Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (userService.getUser(value.toString()) != null) {
FacesMessage message = new FacesMessage(null, "This User ID is already registered. Please logon or choose another one to register.");
throw new ValidatorException(message);
}
}
}
To achieve the desired outcome, you should call renderResponse()
as indicated by the following excerpt from oracle:
If any validate methods or event listeners called renderResponse on the current FacesContext, the JavaServer Faces implementation skips to the render response phase.
But that's the way the validations phase works I'm afraid. From oracle:
During this phase, the JavaServer Faces implementation processes all validators registered on the components in the tree. It examines the component attributes that specify the rules for the validation and compares these rules to the local value stored for the component.
If the local value is invalid, the JavaServer Faces implementation adds an error message to the FacesContext instance
The key phrases here are the processes all validators and adds an error message to the FacesContext instance. While it's not very clear here, what it implies is that the validations phase as a whole will be completed and messages queued. After completion, then it moves to the appropriate phase.