jakartaee / expression-language

Jakarta Expression Language
https://eclipse.org/ee4j/el
Other
68 stars 49 forks source link

EL source returns Integer.class instead of Object.class #36

Closed glassfishrobot closed 10 years ago

glassfishrobot commented 10 years ago

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 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().

Environment

JDeveloper testing

glassfishrobot commented 6 years ago
glassfishrobot commented 10 years ago

@glassfishrobot Commented Reported by pedro.nunes

glassfishrobot commented 10 years ago

@glassfishrobot Commented @edburns said: Maven project, pre-built.

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

glassfishrobot commented 10 years ago

@glassfishrobot Commented @edburns said: Reproducer showing use of custom to perform the conversion based on a prior knowledge.

glassfishrobot commented 10 years ago

@glassfishrobot Commented @edburns said:

**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.

glassfishrobot commented 10 years ago

@glassfishrobot Commented kchung said: Fixed in the trunk.

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

glassfishrobot commented 10 years ago

@glassfishrobot Commented kchung said: 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)
glassfishrobot commented 10 years ago

@glassfishrobot Commented File: 20140107-1658Z-i_bugdb_17959751-reproducer.zip Attached By: @edburns

glassfishrobot commented 10 years ago

@glassfishrobot Commented File: 20140108-1834Z-i_bugdb_17959751.patch Attached By: @edburns

glassfishrobot commented 7 years ago

@glassfishrobot Commented This issue was imported from java.net JIRA UEL-36

glassfishrobot commented 10 years ago

@glassfishrobot Commented Marked as fixed on Tuesday, January 7th 2014, 3:19:31 pm