3

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

Jonathan S. Fisher
  • 8,189
  • 6
  • 46
  • 84

0 Answers0