uel
  1. uel
  2. UEL-36

EL source returns Integer.class instead of Object.class

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None
    • Environment:

      JDeveloper testing

      Description

      The type of the submittedValue for the expression #

      {prop.value} always comes as java.lang.String, even if it is an Integer or Long.

      <af:inputText id="itv" value="#{prop.value}

      " ...>

      The bug occurs because com.sun.el.ValueExpressionImpl.getType() is returning String instead of java.lang.Integer or java.lang.Long.

      As WA for this is to use:
      <h:inputText value="#

      {true ? myBean.properties[0].value : myBean.properties[0].value}

      " />

      After some discussions with Oracle ADF and JSF Developement, the following is the right conclusion and background:

      The JSF spec requires that if no explicit converter is attached to the UIComponent, the type of the property pointed at by the EL expression is used to obtain the converter that will be used to map between the String provided by the Servlet layer and the correct type of the business object. In the case of your example, the EL expression is #

      {prop.value}. When I unwrapped the <ui:repeat> into a linearized list of values, this expression becomes #{myBean.properties[0].value} for indices [0,2). Taking this apart, we see the following:

      "properties[0]" is the 0th element in a List whose elements are Maps.

      "value" is equivalent to doing get("value") on the map at element 0.

      The result will always be of type Object, which the JSF spec says must always be converted to a string unless otherwise configured with an explicit converter.

      Changing the example to use actual JavaBeans properties with the expected types of Integer, Long, and String, and indeed the system behaves as correctly.



      Why would an IF clause would work differently? I'd expect from "#{true ? prop.value : prop.value}" and "#{prop.value}

      " to get the same result, no?

      I expect the reason this is that the ?: operator causes the expression to be evaluated enough so that the thing that is pointed at by prop.value is actually resolved to its value, and when we have the value, its type is evident.

      Because of type erasure, at runtime, the most general type for a Map is Object. This because EL has no way of knowing that the values for the Map is an Integer, for instance. So, JSF seems to be behaving as expected.

      I looked at the EL source to see why getType for #

      {true ? myBean.properties[0].value : myBean.properties[0].value}

      " returns Integer.class instead of Object.class. It turns out that EL is evaluating the expressions and then call getClass() on the result. This behavior is inconsistent with map.getType() and can be an implementation bug.

      What it is desired to get fixed with this bug it is the above inconsistent behviour with map.getType().

      1. 20140108-1834Z-i_bugdb_17959751.patch
        20 kB
        Ed Burns

        Activity

        Hide
        Ed Burns added a comment -

        Maven project, pre-built.

        Will need to tweak pom.xml to re-build.

        Show
        Ed Burns added a comment - Maven project, pre-built. Will need to tweak pom.xml to re-build.
        Hide
        Ed Burns added a comment -

        Reproducer showing use of custom <f:converter> to perform the conversion based on a prior knowledge.

        Show
        Ed Burns added a comment - Reproducer showing use of custom <f:converter> to perform the conversion based on a prior knowledge.
        Hide
        Ed Burns added a comment -
        testPage.xhtml
        <ui:repeat var="prop" value="#{myBean.properties}" varStatus="status">
                   <h:inputText id="itv" value="#{prop.value}"                           
                                    label="#{prop.dispName}" 
                                    validator="#{myBean.validateAttrValue}">
                       <f:converter converterId="myConverter" />
                </h:inputText>
                 <h:outputText  value="#{prop.origtype}" id="ot1"/>
                 <h:outputText  value="----" id="ot3"/>
                <h:outputText value="#{prop.type}" id="ot2"/>
                <br/>
           </ui:repeat>
        
        MyConverter.java
        @FacesConverter("myConverter")
        public class MyConverter implements Converter {
        
            @Override
            public Object getAsObject(FacesContext context, UIComponent component, String value) {
                Object prop = context.getExternalContext().getRequestMap().get("prop");
                
                Object result = value;
                if (prop instanceof Map) {
                    Map<String, Object> cur = (Map<String, Object>) prop;
                    String className = (String) cur.get("origtype");
                    if (null != className) {
                        Class origType;
                        try {
                            origType = Class.forName(className);
                            if (origType.isAssignableFrom(Integer.class)) {
                                result = Integer.valueOf(value).intValue();
                            } else if (origType.isAssignableFrom(Long.class)) {
                                result = Long.valueOf(value).longValue();
                            } else {
                                result = value.toString();
                            }                    
                        } catch (ClassNotFoundException ex) {
                            Logger.getLogger(MyConverter.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                
                return result;
            }
        
            @Override
            public String getAsString(FacesContext context, UIComponent component, Object value) {
                return value.toString();
            }
        

        This is terribly crude and exemplifies one of the things that people dislike about JSF. It does work, however. On the other hand, I do expect there is a way to restructure the objects pointed to by the XHTML page so that the type information is not lost.

        Show
        Ed Burns added a comment - testPage.xhtml <ui:repeat var = "prop" value= "#{myBean.properties}" varStatus= "status" > <h:inputText id= "itv" value= "#{prop.value}" label= "#{prop.dispName}" validator= "#{myBean.validateAttrValue}" > <f:converter converterId= "myConverter" /> </h:inputText> <h:outputText value= "#{prop.origtype}" id= "ot1" /> <h:outputText value= "----" id= "ot3" /> <h:outputText value= "#{prop.type}" id= "ot2" /> <br/> </ui:repeat> MyConverter.java @FacesConverter( "myConverter" ) public class MyConverter implements Converter { @Override public Object getAsObject(FacesContext context, UIComponent component, String value) { Object prop = context.getExternalContext().getRequestMap().get( "prop" ); Object result = value; if (prop instanceof Map) { Map< String , Object > cur = (Map< String , Object >) prop; String className = ( String ) cur.get( "origtype" ); if ( null != className) { Class origType; try { origType = Class .forName(className); if (origType.isAssignableFrom( Integer .class)) { result = Integer .valueOf(value).intValue(); } else if (origType.isAssignableFrom( Long .class)) { result = Long .valueOf(value).longValue(); } else { result = value.toString(); } } catch (ClassNotFoundException ex) { Logger.getLogger(MyConverter.class.getName()).log(Level.SEVERE, null , ex); } } } return result; } @Override public String getAsString(FacesContext context, UIComponent component, Object value) { return value.toString(); } This is terribly crude and exemplifies one of the things that people dislike about JSF. It does work, however. On the other hand, I do expect there is a way to restructure the objects pointed to by the XHTML page so that the type information is not lost.
        Hide
        kchung added a comment -

        Fixed in the trunk.

        For a ?: expression, calls subexpression's getType() instead of the expressions's getValue().getClass().

        Show
        kchung added a comment - Fixed in the trunk. For a ?: expression, calls subexpression's getType() instead of the expressions's getValue().getClass().
        Hide
        kchung added a comment -

        Index: trunk/impl/src/main/java/com/sun/el/parser/AstChoice.java
        ===================================================================

        --- trunk/impl/src/main/java/com/sun/el/parser/AstChoice.java	(revision 388)
        +++ trunk/impl/src/main/java/com/sun/el/parser/AstChoice.java	(revision 389)
        @@ -55,8 +55,9 @@
         
             public Class getType(EvaluationContext ctx)
                     throws ELException {
        -        Object val = this.getValue(ctx);
        -        return (val != null) ? val.getClass() : null;
        +        Object obj0 = this.children[0].getValue(ctx);
        +        Boolean b0 = coerceToBoolean(obj0);
        +        return this.children[((b0.booleanValue() ? 1 : 2))].getType(ctx);
             }
         
             public Object getValue(EvaluationContext ctx)
        
        Show
        kchung added a comment - Index: trunk/impl/src/main/java/com/sun/el/parser/AstChoice.java =================================================================== --- trunk/impl/src/main/java/com/sun/el/parser/AstChoice.java (revision 388) +++ trunk/impl/src/main/java/com/sun/el/parser/AstChoice.java (revision 389) @@ -55,8 +55,9 @@ public Class getType(EvaluationContext ctx) throws ELException { - Object val = this .getValue(ctx); - return (val != null ) ? val.getClass() : null ; + Object obj0 = this .children[0].getValue(ctx); + Boolean b0 = coerceToBoolean(obj0); + return this .children[((b0.booleanValue() ? 1 : 2))].getType(ctx); } public Object getValue(EvaluationContext ctx)

          People

          • Assignee:
            Unassigned
            Reporter:
            pedro.nunes
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: