Are JSF converters supposed to be applied before validators? We have a situation where it seems it's being applied before the converter.
<o:importConstants
type="com.xxx.enums.UsState"
var="UsState" />
<b:selectOneMenu
id="homeStateSelectOneMenu"
value="#{contactPage.contact.homeState}"
label="Home State"
converter="DisplayableTextConverter">
<f:selectItem
id="selectOneUsStateSelectItem"
itemLabel="Select One"
noSelectionOption="true"
itemDisabled="true" />
<f:selectItems
id="usStateSelectItems"
value="#{UsState}" />
</b:selectOneMenu>
and the model object has this field:
@Enumerated(EnumType.STRING)
@Column(name = "home_state")
@NotNull //LYNCHPIN
private UsState homeState;
Converter:
@ApplicationScoped
@FacesConverter(value = "DisplayableTextConverter", managed = true)
public class DisplayableTextConverter implements Converter<DisplayableText> {
public static final String STATE_KEY = "DisplayableTextConverter.forClass";
@SuppressWarnings("unchecked")
@Override
public DisplayableText getAsObject(final FacesContext context, final UIComponent component, String text) {
text = trimToNull(text);
if (text != null) {
final String className = (String) component.getTransientStateHelper().getTransient(STATE_KEY);
try {
return DisplayableText.parseEnum(text, (Class<DisplayableText>) Class.forName(className));
} catch (final ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
@Override
public String getAsString(final FacesContext context, final UIComponent component, final DisplayableText displayableText) {
if (displayableText != null) {
component.getTransientStateHelper().putTransient(STATE_KEY, displayableText.getClass().getName());
return displayableText.getDisplayText();
} else {
return null;
}
}
}
Enum: (truncated)
public enum UsState implements DisplayableText {
AL("Alabama"), AK("Alaska"), AZ("Arizona"), AR("Arkansas"), ....
public final String displayText;
@Override
public String getDisplayText() {
return displayText;
}
UsState(String specificDisplayValue) {
displayText = specificDisplayValue;
}
}
So what's interesting is if we remove @NotNull
, the form submits, and if we add it, the submission fails. Looking at the stack trace, it appears that when the @NotNull
it appears the validator is being called before the converter in certain cases. Why would this be?
Here is No @NotNull
:
java.lang.IllegalArgumentException: Arkansas is not an instance of class com.xxx.UsState
at org.apache.bval.jsr.job.ValidateProperty.<init>(ValidateProperty.java:515)
at org.apache.bval.jsr.job.ValidationJobFactory.validateValue(ValidationJobFactory.java:76)
at org.apache.bval.jsr.ValidatorImpl.validateValue(ValidatorImpl.java:65)
at org.apache.bval.jsr.CascadingPropertyValidator.validateValue(CascadingPropertyValidator.java:99)
at javax.faces.validator.BeanValidator.validate(BeanValidator.java:218)
at javax.faces.component._ComponentUtils.callValidators(_ComponentUtils.java:291)
at javax.faces.component.UIInput.validateValue(UIInput.java:489)
at net.bootsfaces.component.selectOneMenu.SelectOneMenu.validateValue(SelectOneMenu.java:118)
at net.bootsfaces.component.selectOneMenu.SelectOneMenuRenderer.decode(SelectOneMenuRenderer.java:96)
at javax.faces.component.UIComponentBase.decode(UIComponentBase.java:479)
at javax.faces.component.UIInput.decode(UIInput.java:371)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1408)
at javax.faces.component.UIInput.processDecodes(UIInput.java:207)
at javax.faces.component.UIComponentBase.processDecodes(UIComponentBase.java:1402)
at javax.faces.component.UIForm.processDecodes(UIForm.java:154)
at org.apache.myfaces.context.servlet.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:775)
and here is without the @NotNull
(breakpoint in DisplayableTextConverter
):
at com.xxx.jsf.converter.DisplayableTextConverter.getAsObject(DisplayableTextConverter.java:21)
at com.xxx.jsf.converter.DisplayableTextConverter.getAsObject(DisplayableTextConverter.java:1)
at org.apache.myfaces.cdi.converter.FacesConverterCDIWrapper.getAsObject(FacesConverterCDIWrapper.java:63)
at net.bootsfaces.render.CoreRenderer.getConvertedValue(CoreRenderer.java:532)
at javax.faces.component.UIInput.getConvertedValue(UIInput.java:789)
at javax.faces.component.UIInput.validate(UIInput.java:708)
at javax.faces.component.UIInput.processValidators(UIInput.java:293)
at javax.faces.component.UIComponentBase.processValidators(UIComponentBase.java:1458)
at javax.faces.component.UIForm.processValidators(UIForm.java:210)
at org.apache.myfaces.context.servlet.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:779)
Thanks! This is running on Apache TomEE 8.0.6 but with MyFaces 2.3.8, Bootsfaces 1.5.0