<h:selectManyCheckbox> 的 ClassCastException,在 INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL 处于活动状态时对 ajax 更新进行验证

ClassCastException for <h:selectManyCheckbox> with validation on ajax update when INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL is active

我有一个 <h:selectManyCheckbox> 需要验证。如果我提交表单,当没有选择任何内容时我会收到验证错误。到目前为止,这是预期的。但是,如果我在复选框上执行 ajax 更新,我会得到 ClassCastException。但前提是空值被视为 null.

所以,我有以下设置。在 web.xml 我设置

<context-param>
  <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
  <param-value>true</param-value>
</context-param>

然后我有一个像这样的 xhtml 页面:

<h:form id="main">
  <h:selectManyCheckbox id="value" value="#{testcb.selected}" required="true" requiredMessage="Select at least one entry">
    <f:selectItems value="#{testcb.available}"/>
  </h:selectManyCheckbox>
  <div><h:message for="value" style="color:red;"/></div>

  <h:outputLabel for="checkit" value="Enter some text: "/>
  <h:inputText id="checkit" value="#{testcb.text}">
    <f:ajax event="change" execute="@this" render=":main:value"/>
  </h:inputText>

  <div><h:commandButton type="submit" value="Submit" action="#{testcb.action}"/></div>
</h:form>

而这个辅助 bean:

@Named("testcb")
@SessionScoped
public class TestCBBean implements Serializable {

  private final Set<TestValue> available = EnumSet.allOf(TestValue.class);
  private final Set<TestValue> selected = EnumSet.noneOf(TestValue.class);
  private String text;

  public void action() {}

  public Set<TestValue> getAvailable() { return available; }

  public void setAvailable(Set<TestValue> available) {
    this.available.clear();
    this.available.addAll(available);
  }

  public Set<TestValue> getSelected() { return selected; }

  public void setSelected(Set<TestValue> selected) {
    this.selected.clear();
    this.selected.addAll(selected);
  }

  public String getText() { return text; }

  public void setText(String text) { this.text = text; }
}

还有这个enum

public enum TestValue { ONE, TWO, THREE }

我在 Wildfly 26.0.1-Final (JavaEE 8) 中 运行 这个。但这也发生在旧版本中(如 Wildfly 15)。我在做什么:

  1. 输入一些文本并保留该框:ajax 更新运行并在模型中成功设置值
  2. 我按下提交:弹出空复选框的验证错误
  3. 我修改输入中的文本并保留框:ajax 更新结果出现以下异常:
    java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.MenuRenderer.getSubmittedSelectedValues(MenuRenderer.java:508)
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:89)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1655)
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:628)
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:159)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIForm.visitTree(UIForm.java:355)

在 ajax 更新中未提交复选框。但是它们似乎包含一个空字符串作为之前验证步骤的提交值。

将上下文参数设置为 false 时,此方法有效。但我想将其保留在 true。我有什么想法可以解决这个问题吗?

已转载。这确实是 Mojarra 中的一个错误。

归结为UIInput超类中的以下方法...

@Override
public Object getSubmittedValue() {
    if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) {
        return "";
    } else {
        return submittedValue;
    }
}

... UISelectMany 超类中以 returns new String[0] 而不是 "" 的方式被覆盖.这是实施 Faces issue 671.

期间的疏忽

我已经在 Mojarra issue 5081 中修复了它。

与此同时,在升级到包含修复程序的 Mojarra 版本之前,您可以通过将 UISelectMany 的整个 source code 文件复制粘贴到您的项目中,同时维护打包并向其中添加以下方法:

@Override
public Object getSubmittedValue() {
    Object submittedValue = super.getSubmittedValue();
    return "".equals(submittedValue) ? new String[0] : submittedValue;
}