MyFaces Core
  1. MyFaces Core
  2. MYFACES-3304

NullPointerException using h:selectOneRadio with an enum

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 2.0.8
    • Fix Version/s: 2.0.10, 2.1.4
    • Component/s: None
    • Labels:
      None
    • Environment:
      Ubuntu 11.0.4, Jetty 6.1.10, JDK 1.6, Myfaces Core 2.0.8, Primefaces 3.0.M3

      Description

      Trying to use a h:selectOneRadio to select one of two values for an enum and it fails, throwing a NullPointerException. No explicit converter is in use so (from debugging) it appears to be using the default EnumConverter.

      Code snippets in question are as follows:

      testLovs.xhtml:

      <h:panelGrid columns="1">

      Simple radio button with constant string values
      <h:selectOneRadio id="l1" value="#

      {testLovsBean.l1}

      ">
      <f:selectItem itemValue="A" itemLabel="labelA"/>
      <f:selectItem itemValue="B" itemLabel="labelB"/>
      </h:selectOneRadio>
      <h:outputText id="l1Str" value="l1: #

      {testLovBean.l1AsString}

      "/>

      <p:separator/>

      Radio button for enum
      <h:selectOneRadio id="l2" value="#

      {testLovsBean.l2}

      ">
      <f:selectItem itemValue="#

      {VALUEA}

      " itemLabel="labelA"/>
      <f:selectItem itemValue="#

      {VALUEB}

      " itemLabel="labelB"/>
      </h:selectOneRadio>
      <h:outputText id="l2Str" value="l2: #

      {testLovBean.l2AsString}

      "/>

      <p:separator/>

      <p:commandButton id="commitCommand"
      action="#

      {testLovsBean.commitAction}

      "
      value="Submit"
      ajax="false"/>

      TestLovsBean.java:

      package tn.view.bean.test;

      import org.springframework.context.annotation.Scope;
      import org.springframework.stereotype.Component;

      import tn.view.util.FacesUtils;

      /**

      • Class used for testing date controls
        */
        @Component
        @Scope("request")
        public class TestLovsBean
        {
        public TestLovsBean()
        {
        }

      public String getL1()

      { return _l1; }

      public void setL1(String l1)
      { _l1 = l1; }

      public String getL1AsString()
      { return _l1; }

      public TestEnum getL2()

      { return _l2; }

      public void setL2(TestEnum l2)

      { _l2 = l2; }

      public String commitAction()

      { System.out.println("commitAction invoked"); FacesUtils.addInfoMessage("L1: " + _l1); FacesUtils.addInfoMessage("L2: " + _l2); return null; }

      private String _l1;
      private TestEnum _l2;
      }

      TestEnum.java:

      package tn.view.bean.test;

      public enum TestEnum

      { VALUEA, VALUEB, }

      Stack trace:

      javax.servlet.ServletException
      at javax.faces.webapp.FacesServlet.service(FacesServlet.java:221)
      at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
      at tn.view.error.ResponseCapturingFilter.doFilter(ResponseCapturingFilter.java:83)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
      at tn.view.error.AbstractUncaughtExceptionInterceptor.doFilter(AbstractUncaughtExceptionInterceptor.java:66)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:292)
      at net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:108)
      at net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter.doFilter(SecurityEnforcementFilter.java:197)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
      at net.sf.acegisecurity.providers.anonymous.AnonymousProcessingFilter.doFilter(AnonymousProcessingFilter.java:143)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
      at net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter.doFilter(BasicProcessingFilter.java:214)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
      at net.sf.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:324)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
      at net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:220)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
      at net.sf.acegisecurity.securechannel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:168)
      at tn.security.CustomChannelProcessingFilter.doFilter(CustomChannelProcessingFilter.java:23)
      at net.sf.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
      at net.sf.acegisecurity.util.FilterChainProxy.doFilter(FilterChainProxy.java:173)
      at net.sf.acegisecurity.util.FilterToBeanProxy.doFilter(FilterToBeanProxy.java:120)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
      at net.sf.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:50)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
      at tn.view.filter.StaticContentCachingFilter.doFilter(StaticContentCachingFilter.java:85)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
      at tn.view.error.AbstractUncaughtExceptionFilter.doFilter(AbstractUncaughtExceptionFilter.java:81)
      at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
      at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
      at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
      at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
      at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
      at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
      at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
      at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
      at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:211)
      at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
      at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
      at org.mortbay.jetty.Server.handle(Server.java:313)
      at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:506)
      at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:830)
      at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:514)
      at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
      at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:381)
      at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:227)
      at org.mortbay.jetty.security.SslSocketConnector$SslConnection.run(SslSocketConnector.java:626)
      at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:442)
      Caused by: java.lang.NullPointerException
      at org.apache.myfaces.shared.renderkit.html.HtmlRadioRendererBase.renderGroupOrItemRadio(HtmlRadioRendererBase.java:221)
      at org.apache.myfaces.shared.renderkit.html.HtmlRadioRendererBase.encodeEnd(HtmlRadioRendererBase.java:126)
      at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:539)
      at org.apache.myfaces.shared.renderkit.RendererUtils.renderChild(RendererUtils.java:551)
      at org.apache.myfaces.shared.renderkit.html.HtmlGridRendererBase.renderChildren(HtmlGridRendererBase.java:334)
      at org.apache.myfaces.shared.renderkit.html.HtmlGridRendererBase.encodeEnd(HtmlGridRendererBase.java:169)
      at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:539)
      at org.primefaces.renderkit.CoreRenderer.renderChild(CoreRenderer.java:64)
      at org.primefaces.renderkit.CoreRenderer.renderChildren(CoreRenderer.java:48)
      at org.primefaces.renderkit.CoreRenderer.renderChild(CoreRenderer.java:62)
      at org.primefaces.renderkit.CoreRenderer.renderChildren(CoreRenderer.java:48)
      at org.primefaces.component.layout.LayoutUnitRenderer.encodeEnd(LayoutUnitRenderer.java:51)
      at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:539)
      at javax.faces.component.UIComponent.encodeAll(UIComponent.java:641)
      at javax.faces.component.UIComponent.encodeAll(UIComponent.java:637)
      at javax.faces.component.UIComponent.encodeAll(UIComponent.java:637)
      at javax.faces.component.UIComponent.encodeAll(UIComponent.java:637)
      at org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage.renderView(FaceletViewDeclarationLanguage.java:1481)
      at org.apache.myfaces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:264)
      at org.apache.myfaces.lifecycle.RenderResponseExecutor.execute(RenderResponseExecutor.java:90)
      at org.apache.myfaces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:239)
      at javax.faces.webapp.FacesServlet.service(FacesServlet.java:191)
      ... 49 more

        Activity

        Hide
        Michael Kurz added a comment -

        A shot in the dark: I would say this does not work because the following two lines don't work as expected:

        <f:selectItem itemValue="#

        {VALUEA}

        " itemLabel="labelA"/>
        <f:selectItem itemValue="#

        {VALUEB}

        " itemLabel="labelB"/>

        Unless you have a special ELResolver that resolves VALUEA and VALUEB (which I guess you don't have) the values will resolve to null. You should create SelectItem instances in the managed bean (preferred) or create a property on the bean for each enum constants.

        Nevertheless, there should not be a NPE in HtmlRadioRendererBase.java:221. The problem is, that EnumConverter returns null in this case for itemStrValue. Easiest solution would be to set itemStrValue to "" if it is null.

        Any objections? Leo?

        Show
        Michael Kurz added a comment - A shot in the dark: I would say this does not work because the following two lines don't work as expected: <f:selectItem itemValue="# {VALUEA} " itemLabel="labelA"/> <f:selectItem itemValue="# {VALUEB} " itemLabel="labelB"/> Unless you have a special ELResolver that resolves VALUEA and VALUEB (which I guess you don't have) the values will resolve to null. You should create SelectItem instances in the managed bean (preferred) or create a property on the bean for each enum constants. Nevertheless, there should not be a NPE in HtmlRadioRendererBase.java:221. The problem is, that EnumConverter returns null in this case for itemStrValue. Easiest solution would be to set itemStrValue to "" if it is null. Any objections? Leo?
        Hide
        Leonardo Uribe added a comment -

        The following notations work:

        <f:selectItem itemValue="#

        {'VALUEA'}

        " itemLabel="labelA"/>
        <f:selectItem itemValue="#

        {'VALUEB'}

        " itemLabel="labelB"/>

        <f:selectItem itemValue="VALUEA" itemLabel="labelA"/>
        <f:selectItem itemValue="VALUEB" itemLabel="labelB"/>

        public TestEnum getValueA()

        { return TestEnum.VALUEA; }

        public TestEnum getValueB()

        { return TestEnum.VALUEB; }

        <f:selectItem itemValue="#

        {testLovsBean.valueA}

        " itemLabel="labelA"/>
        <f:selectItem itemValue="#

        {testLovsBean.valueB}

        " itemLabel="labelB"/>

        So if #

        {VALUEA}

        and #

        {VALUEB}

        resolve to the expected enum value it should work. The syntax used here is just invalid.

        But the example shows another problem. I tried the same example with mojarra and the page is rendered and the null option is a valid choice, but as soon as you select it on the next request the selected radio button is unselected. If you have a selectOne component attached to an enum, it is valid to add a null option (but in practice if you are using an enum you usually use another option on the enum). In that case, EnumConverter will fail to convert that null value into a valid enum value anyway but you still can provide a custom converter that check the null value and deal with it.

        The other problem with this component is if you don't set value="", when the form is submitted the value "on" will be passed (tried on firefox), so set itemStrValue to "" is reasonable, but at the end this is resposibility of the code that renders at the end the component. The reason why in mojarra that option is unselected as soon as is selected and submited is "" != null, so when "" is received, it is not associated to the null option.

        In conclusion, we should "at least" behave the same as Mojarra in this case. But I still have my doubts about that. For example, someone can say we should honor javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL web config param in this case. It that param is set to true, as soon as a "" is received, it is converted to null, and since there is a null option, this option should be selected. But if this is false, for this specific component an empty "" is ALWAYS the same as a null value.

        I think we should fix some parts of the code to reflect that. First of all check for null before render an option value and render "" in that case. Second take into account that for this specific case an empty "" is ALWAYS the same as a null value. And finally don't return "" when a String value for the renderer is calculated and return null, just for keep things clear. I'll attach a patch for this one, but first we need to check if the change does not make the TCK fails before commit.

        Show
        Leonardo Uribe added a comment - The following notations work: <f:selectItem itemValue="# {'VALUEA'} " itemLabel="labelA"/> <f:selectItem itemValue="# {'VALUEB'} " itemLabel="labelB"/> <f:selectItem itemValue="VALUEA" itemLabel="labelA"/> <f:selectItem itemValue="VALUEB" itemLabel="labelB"/> public TestEnum getValueA() { return TestEnum.VALUEA; } public TestEnum getValueB() { return TestEnum.VALUEB; } <f:selectItem itemValue="# {testLovsBean.valueA} " itemLabel="labelA"/> <f:selectItem itemValue="# {testLovsBean.valueB} " itemLabel="labelB"/> So if # {VALUEA} and # {VALUEB} resolve to the expected enum value it should work. The syntax used here is just invalid. But the example shows another problem. I tried the same example with mojarra and the page is rendered and the null option is a valid choice, but as soon as you select it on the next request the selected radio button is unselected. If you have a selectOne component attached to an enum, it is valid to add a null option (but in practice if you are using an enum you usually use another option on the enum). In that case, EnumConverter will fail to convert that null value into a valid enum value anyway but you still can provide a custom converter that check the null value and deal with it. The other problem with this component is if you don't set value="", when the form is submitted the value "on" will be passed (tried on firefox), so set itemStrValue to "" is reasonable, but at the end this is resposibility of the code that renders at the end the component. The reason why in mojarra that option is unselected as soon as is selected and submited is "" != null, so when "" is received, it is not associated to the null option. In conclusion, we should "at least" behave the same as Mojarra in this case. But I still have my doubts about that. For example, someone can say we should honor javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL web config param in this case. It that param is set to true, as soon as a "" is received, it is converted to null, and since there is a null option, this option should be selected. But if this is false, for this specific component an empty "" is ALWAYS the same as a null value. I think we should fix some parts of the code to reflect that. First of all check for null before render an option value and render "" in that case. Second take into account that for this specific case an empty "" is ALWAYS the same as a null value. And finally don't return "" when a String value for the renderer is calculated and return null, just for keep things clear. I'll attach a patch for this one, but first we need to check if the change does not make the TCK fails before commit.
        Hide
        Matt Benson added a comment -

        What am I missing that isn't handled by setting the org.apache.myfaces.ENUM_CONVERTER_ALLOW_STRING_PASSTHROUGH config parameter to true?

        Show
        Matt Benson added a comment - What am I missing that isn't handled by setting the org.apache.myfaces.ENUM_CONVERTER_ALLOW_STRING_PASSTHROUGH config parameter to true?
        Hide
        Leonardo Uribe added a comment -

        In this case no. The reason is the renderer for h:selectOneRadio check if a "" was received as submitted value, convert it to null and pass it to EnumConverter, so it will never see "". This is an extract of the code:

        public static Object getConvertedUISelectOneValue(FacesContext facesContext, UISelectOne output, Object submittedValue){
        if (submittedValue != null && !(submittedValue instanceof String))

        { throw new IllegalArgumentException("Submitted value of type String for component : " + getPathToComponent(output) + "expected"); }

        //To be compatible with jsf ri, and according to issue 69
        //[ Permit the passing of a null value to SelectItem.setValue() ]
        //If submittedValue == "" then convert to null.
        if ((submittedValue != null) && (submittedValue instanceof String) && ("".equals(submittedValue)))
        {
        //Replace "" by null value
        submittedValue = null;

        Note the value stored on the component is not updated, so it still is "". That's the problem. We need to do the changes described on the patch, so if the null option is selected it keeps selected after a postback and render value="" when null value is received, but just before the option is rendered, for the other calculations keep the value as it was received.

        Note this reasoning suppose Mojarra has a problem too in this part. I checked against TCK and we can commit the code. If no objections I'll commit this code soon.

        Show
        Leonardo Uribe added a comment - In this case no. The reason is the renderer for h:selectOneRadio check if a "" was received as submitted value, convert it to null and pass it to EnumConverter, so it will never see "". This is an extract of the code: public static Object getConvertedUISelectOneValue(FacesContext facesContext, UISelectOne output, Object submittedValue){ if (submittedValue != null && !(submittedValue instanceof String)) { throw new IllegalArgumentException("Submitted value of type String for component : " + getPathToComponent(output) + "expected"); } //To be compatible with jsf ri, and according to issue 69 //[ Permit the passing of a null value to SelectItem.setValue() ] //If submittedValue == "" then convert to null. if ((submittedValue != null) && (submittedValue instanceof String) && ("".equals(submittedValue))) { //Replace "" by null value submittedValue = null; Note the value stored on the component is not updated, so it still is "". That's the problem. We need to do the changes described on the patch, so if the null option is selected it keeps selected after a postback and render value="" when null value is received, but just before the option is rendered, for the other calculations keep the value as it was received. Note this reasoning suppose Mojarra has a problem too in this part. I checked against TCK and we can commit the code. If no objections I'll commit this code soon.

          People

          • Assignee:
            Leonardo Uribe
            Reporter:
            Joe Rossi
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development