MyFaces Core
  1. MyFaces Core
  2. MYFACES-3168

Bound attribute values resolve to NULL during PreRenderViewEvent for nested composite components

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Invalid
    • Affects Version/s: 2.0.6, 2.1.0
    • Fix Version/s: None
    • Component/s: General
    • Labels:
      None
    • Environment:
      Windows 7 x64 Enterprise.

      JDK 1.6.0_25.

      Eclipse Version: Helios Service Release 2
      Build id: 20110218-0911

      Description

      When nesting custom composite components, any data bound attributes (eg #

      {someValueExpression}

      ) will resolve to null during the PreRenderViewEvent. This only occurs on the second level or deeper nested component (the top-level component in the page works fine).

      This same issue occurs on JSF RI 2.0.4, 2.1.x, 2.2.x

      I have an eclipse project for upload that reproduces the problem.

      I have no cause for the issue at this time.

      Cheers,

      Matt

      1. ASF.LICENSE.NOT.GRANTED--screenshot-1.jpg
        67 kB
        MAtthew Sweeney
      2. jsf-testing-2.zip
        3.28 MB
        Kennard Consulting
      3. jsf-testing-myfaces.zip
        5.89 MB
        MAtthew Sweeney

        Activity

        Hide
        Ludovic Pénet added a comment - - edited

        I also request a reopening of this issue. Without this issue solved (or a work around provided), one can not easily forge EL expressions for dynamic controls. I mean, controls dynamically instantiated on a PreRenderViewEvent.

        To be able to forge "complex" EL expressions involving a variable passed as a parameter, I today use a klude that recursively resolve this variable to its "real" value :

        class JSFUtils {
        public static String getValueExpressionExpression(ValueExpression valueExpression) {
        return valueExpression.getExpressionString().replace("#

        {", "").replace("}", "");
        }

        public static String getMappedValueExpression(ValueExpression valueExpression) {
        ContextAwareTagValueExpression ctxAware = (ContextAwareTagValueExpression)valueExpression;
        if(ctxAware != null) { return getMappedValueExpression((WrappedValueExpression)ctxAware.getWrapped()); }
        return getValueExpressionExpression(valueExpression);
        }

        public static String getMappedValueExpression(WrappedValueExpression wrappedExpression) {
        String exprString = wrappedExpression.getExpressionString().replace("#{", "").replace("}

        ", "");
        String ret = exprString;
        try {

        Field valueExpression = WrappedValueExpression.class.getDeclaredField("valueExpression");
        valueExpression.setAccessible(true);
        ValueExpressionImpl vei = (ValueExpressionImpl) valueExpression.get(wrappedExpression);
        Field varMapper = ValueExpressionImpl.class.getDeclaredField("varMapper");
        varMapper.setAccessible(true);
        VariableMapperImpl vmi = (VariableMapperImpl) varMapper.get(vei);
        if(vmi != null) {
        String[] components = exprString.split("
        .");
        components[0] = getMappedValueExpression(vmi.resolveVariable(components[0]));
        ret = "";
        for(int i = 0 ; i < components.length ; i++) {
        if(i != 0)

        { ret += "."; }

        ret += components[i];
        }
        }
        } catch (Exception ex)

        { logger.error("Exception lors du mapping de l'expression EL " + exprString, ex); }

        finally

        { return ret; }

        }
        }

        For a control such as

        <my:custom param=#

        {object}

        "/>

        It can then be used like

        String myParamVar = JSFUtils.getMappedValueExpression(this.getValueExpression("param"));

        It would be great to have such functionnality available without naughty kludges like introspection... Maybe a myfaces extension would fit ?

        Thanks in advance for your attention.

        Show
        Ludovic Pénet added a comment - - edited I also request a reopening of this issue. Without this issue solved (or a work around provided), one can not easily forge EL expressions for dynamic controls. I mean, controls dynamically instantiated on a PreRenderViewEvent. To be able to forge "complex" EL expressions involving a variable passed as a parameter, I today use a klude that recursively resolve this variable to its "real" value : class JSFUtils { public static String getValueExpressionExpression(ValueExpression valueExpression) { return valueExpression.getExpressionString().replace("# {", "").replace("}", ""); } public static String getMappedValueExpression(ValueExpression valueExpression) { ContextAwareTagValueExpression ctxAware = (ContextAwareTagValueExpression)valueExpression; if(ctxAware != null) { return getMappedValueExpression((WrappedValueExpression)ctxAware.getWrapped()); } return getValueExpressionExpression(valueExpression); } public static String getMappedValueExpression(WrappedValueExpression wrappedExpression) { String exprString = wrappedExpression.getExpressionString().replace("#{", "").replace("} ", ""); String ret = exprString; try { Field valueExpression = WrappedValueExpression.class.getDeclaredField("valueExpression"); valueExpression.setAccessible(true); ValueExpressionImpl vei = (ValueExpressionImpl) valueExpression.get(wrappedExpression); Field varMapper = ValueExpressionImpl.class.getDeclaredField("varMapper"); varMapper.setAccessible(true); VariableMapperImpl vmi = (VariableMapperImpl) varMapper.get(vei); if(vmi != null) { String[] components = exprString.split(" ."); components [0] = getMappedValueExpression(vmi.resolveVariable(components [0] )); ret = ""; for(int i = 0 ; i < components.length ; i++) { if(i != 0) { ret += "."; } ret += components [i] ; } } } catch (Exception ex) { logger.error("Exception lors du mapping de l'expression EL " + exprString, ex); } finally { return ret; } } } For a control such as <my:custom param=# {object} "/> It can then be used like String myParamVar = JSFUtils.getMappedValueExpression(this.getValueExpression("param")); It would be great to have such functionnality available without naughty kludges like introspection... Maybe a myfaces extension would fit ? Thanks in advance for your attention.
        Hide
        Kennard Consulting added a comment -

        Leonardo,

        The equivalent issue under Mojarra is http://java.net/jira/browse/JAVASERVERFACES-2089. Ed Burns has recently started work on it. Would you care to comment on Ed's patch, given your understanding of this issue?

        Regards,

        Richard.

        Show
        Kennard Consulting added a comment - Leonardo, The equivalent issue under Mojarra is http://java.net/jira/browse/JAVASERVERFACES-2089 . Ed Burns has recently started work on it. Would you care to comment on Ed's patch, given your understanding of this issue? Regards, Richard.
        Hide
        Leonardo Uribe added a comment -

        Hi Richard

        I checked the comment on metawidget forum and it seems like a typo error. The datatable definition is:

        <h:dataTable id="communications" value="#

        {contact.currentCommunications}

        " var="_communication"

        but the metawidget usage is this:

        <m:metawidget value="#

        {communication.current.value}

        " rendererType="simple" rendered="#

        {!contact.readOnly}"/>

        It should be

        <m:metawidget value="#{_communication.current.value}" rendererType="simple" rendered="#{!contact.readOnly}

        "/>

        Now about this bug: I think we have done everything we can. I have discussed earlier this problem with other people. The last thing done was on MYFACES-3289. Here are some comments discussed earlier:

        ?? >> Also your workarounds of finding the components by executing findComponent
        ?? >> are not really safe: findComponent does not setup the context of the component
        ?? >> which you would need to have. Only invokeOnComponent or a treevisit can do
        ?? >> this for you!

        LU >> Yes, I know, but note both invokeOnComponent and visitTree requires a clientId.
        LU >> The problem is for example when the component is inside a datatable. There is
        LU >> only one real UIComponent instance, but it could be many "virtual" instances
        LU >> per row. Since PreRenderViewEvent is propagated to UIViewRoot, there is no
        LU >> associate context, so the only way to do it is create a findComponent expression
        LU >> to locate the real component instance.

        LU >> I know in this case a invokeOnComponent or visitTree with a "findComponent
        LU >> expression" sounds better, but there is nothing on the spec that helps. Usually
        LU >> the tasks done in that location does not require the context, so it is valid to
        LU >> simplify this case and expect only one call per real component. In conclusion,
        LU >> I agree with the solution done in mojarra for this one, even if it is not the
        LU >> best we can do in this case.

        LU >> Anyway, there is something missing on the spec. Some weeks ago, I sent a mail
        LU >> to the EG list under the name:

        LU >> [jsr344-experts] Referencing composite component attributes in child components
        LU >> outside of a tree traversal

        LU >> I think at the end there is something related to this one.

        It would be great to have a way to call a visitTree or invokeOnComponent using a "find component expression". In that way, from PreRenderViewEvent the developer can set up the context using such call. For now all that should be done outside the spec.

        Show
        Leonardo Uribe added a comment - Hi Richard I checked the comment on metawidget forum and it seems like a typo error. The datatable definition is: <h:dataTable id="communications" value="# {contact.currentCommunications} " var="_communication" but the metawidget usage is this: <m:metawidget value="# {communication.current.value} " rendererType="simple" rendered="# {!contact.readOnly}"/> It should be <m:metawidget value="#{_communication.current.value}" rendererType="simple" rendered="#{!contact.readOnly} "/> Now about this bug: I think we have done everything we can. I have discussed earlier this problem with other people. The last thing done was on MYFACES-3289 . Here are some comments discussed earlier: ?? >> Also your workarounds of finding the components by executing findComponent ?? >> are not really safe: findComponent does not setup the context of the component ?? >> which you would need to have. Only invokeOnComponent or a treevisit can do ?? >> this for you! LU >> Yes, I know, but note both invokeOnComponent and visitTree requires a clientId. LU >> The problem is for example when the component is inside a datatable. There is LU >> only one real UIComponent instance, but it could be many "virtual" instances LU >> per row. Since PreRenderViewEvent is propagated to UIViewRoot, there is no LU >> associate context, so the only way to do it is create a findComponent expression LU >> to locate the real component instance. LU >> I know in this case a invokeOnComponent or visitTree with a "findComponent LU >> expression" sounds better, but there is nothing on the spec that helps. Usually LU >> the tasks done in that location does not require the context, so it is valid to LU >> simplify this case and expect only one call per real component. In conclusion, LU >> I agree with the solution done in mojarra for this one, even if it is not the LU >> best we can do in this case. LU >> Anyway, there is something missing on the spec. Some weeks ago, I sent a mail LU >> to the EG list under the name: LU >> [jsr344-experts] Referencing composite component attributes in child components LU >> outside of a tree traversal LU >> I think at the end there is something related to this one. It would be great to have a way to call a visitTree or invokeOnComponent using a "find component expression". In that way, from PreRenderViewEvent the developer can set up the context using such call. For now all that should be done outside the spec.
        Hide
        Kennard Consulting added a comment -

        Hi guys,

        Another one of my users has encountered this same bug. See: https://sourceforge.net/projects/metawidget/forums/forum/747623/topic/4765688

        It is the same problem I described above: if the 'outer' component is an h:column, you cannot make it call pushComponentToEL. So any inner components that are working off PreRenderViewEvent will fail.

        Can you please reopen this bug?

        Regards,

        Richard.

        Show
        Kennard Consulting added a comment - Hi guys, Another one of my users has encountered this same bug. See: https://sourceforge.net/projects/metawidget/forums/forum/747623/topic/4765688 It is the same problem I described above: if the 'outer' component is an h:column, you cannot make it call pushComponentToEL. So any inner components that are working off PreRenderViewEvent will fail. Can you please reopen this bug? Regards, Richard.
        Hide
        Kennard Consulting added a comment -

        Hi guys,

        I have stumbled across something very similar to this issue, and have slightly modified Matt's jsf-testing.zip to demonstrate.

        Basically Matt was using his own composite component as the 'outer' component. If he added pushComponentToEL to it, as Leonardo suggested, he could fix his issue. But what if the 'outer' component is out of your control? In my case it is an h:column. There is no opportunity to call pushComponentToEL on the h:column, and calling it on the 'inner' component has no effect (as is to be expected). Is there a workaround for this case?

        Regards,

        Richard.

        Show
        Kennard Consulting added a comment - Hi guys, I have stumbled across something very similar to this issue, and have slightly modified Matt's jsf-testing.zip to demonstrate. Basically Matt was using his own composite component as the 'outer' component. If he added pushComponentToEL to it, as Leonardo suggested, he could fix his issue. But what if the 'outer' component is out of your control? In my case it is an h:column. There is no opportunity to call pushComponentToEL on the h:column, and calling it on the 'inner' component has no effect (as is to be expected). Is there a workaround for this case? Regards, Richard.
        Hide
        Kennard Consulting added a comment -

        Slightly modified version of this issue, which uses an h:column as the 'outer' component

        Show
        Kennard Consulting added a comment - Slightly modified version of this issue, which uses an h:column as the 'outer' component
        Hide
        Leonardo Uribe added a comment -

        It is something I know, because I tried something similar long time ago. There is no any documentation about it.

        Show
        Leonardo Uribe added a comment - It is something I know, because I tried something similar long time ago. There is no any documentation about it.
        Hide
        MAtthew Sweeney added a comment -

        Hi Leonardo, thanks very much for the comments. I have not had a chance to check this out on my end yet but it does make sense to me and if it resolves the issue then well, it's no longer an issue!

        I didn't know about registering for the event in that fashion, could you possibly point me towards any documentation on this or is it just something to know?

        Regards,

        Matt Sweeney

        Show
        MAtthew Sweeney added a comment - Hi Leonardo, thanks very much for the comments. I have not had a chance to check this out on my end yet but it does make sense to me and if it resolves the issue then well, it's no longer an issue! I didn't know about registering for the event in that fashion, could you possibly point me towards any documentation on this or is it just something to know? Regards, Matt Sweeney
        Hide
        Leonardo Uribe added a comment -

        Thanks for the example provided. It makes easier to detect what's going on.

        The code proposed has a flaw. It is more a side effect, caused by the evaluation of #

        {cc}

        and the fact that the component stack is not set on that location.

        PreRenderViewEvent is an event propagated to UIViewRoot. In this case, you are using a component to listen to that event, so the component stack is not set and when the expressions are resolved, they can't found the right composite component instance and the final result is the one you described.

        The solution is very simple to do. Just try this:

        private void processPreRenderViewEvent(PreRenderViewEvent e) throws AbortProcessingException {

        FacesContext ctx = getFacesContext();

        if(!ctx.isPostback()) {

        this.pushComponentToEL(ctx, this);
        try

        { /** .... **/ }

        finally

        { this.popComponentFromEL(ctx); }

        }

        I tested it and it works. I tried to do something on UIViewRoot, but it doesn't seem to be correct, because it is an event tied to the view, so it should be responsibility of the developer to set context, in that case put the current component onto the stack.

        In other topic, this is a better way to suscribe the event:

        @ListenerFor(systemEventClass=PostAddToViewEvent.class)
        public class UIComponent1 extends UIComponentBase implements NamingContainer, SystemEventListener {

        public UIComponent1() {
        }

        @Override
        public void processEvent(ComponentSystemEvent event)
        throws AbortProcessingException
        {
        super.processEvent(event);

        if (event instanceof PostAddToViewEvent)

        { getFacesContext().getViewRoot().subscribeToViewEvent(PreRenderViewEvent.class, this); }

        }

        In few words, use PostAddToViewEvent to suscribe the listener is better because the component is now attached to the view, so that is the right time to do that. Anyway, do it inside the constructor works. I'll close this issue as invalid.

        Show
        Leonardo Uribe added a comment - Thanks for the example provided. It makes easier to detect what's going on. The code proposed has a flaw. It is more a side effect, caused by the evaluation of # {cc} and the fact that the component stack is not set on that location. PreRenderViewEvent is an event propagated to UIViewRoot. In this case, you are using a component to listen to that event, so the component stack is not set and when the expressions are resolved, they can't found the right composite component instance and the final result is the one you described. The solution is very simple to do. Just try this: private void processPreRenderViewEvent(PreRenderViewEvent e) throws AbortProcessingException { FacesContext ctx = getFacesContext(); if(!ctx.isPostback()) { this.pushComponentToEL(ctx, this); try { /** .... **/ } finally { this.popComponentFromEL(ctx); } } I tested it and it works. I tried to do something on UIViewRoot, but it doesn't seem to be correct, because it is an event tied to the view, so it should be responsibility of the developer to set context, in that case put the current component onto the stack. In other topic, this is a better way to suscribe the event: @ListenerFor(systemEventClass=PostAddToViewEvent.class) public class UIComponent1 extends UIComponentBase implements NamingContainer, SystemEventListener { public UIComponent1() { } @Override public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { super.processEvent(event); if (event instanceof PostAddToViewEvent) { getFacesContext().getViewRoot().subscribeToViewEvent(PreRenderViewEvent.class, this); } } In few words, use PostAddToViewEvent to suscribe the listener is better because the component is now attached to the view, so that is the right time to do that. Anyway, do it inside the constructor works. I'll close this issue as invalid.
        Hide
        MAtthew Sweeney added a comment -

        FYI: Final data-binding works fine, as is evident by the data in the data-bound TextBox in the second level component.

        Show
        MAtthew Sweeney added a comment - FYI: Final data-binding works fine, as is evident by the data in the data-bound TextBox in the second level component.
        Hide
        MAtthew Sweeney added a comment -

        Shot of example project showing bug results. Two different composite components, one nested inside the other. Second level component shows bound data attribute resolving to NULL during PreRenderViewEvent.

        Show
        MAtthew Sweeney added a comment - Shot of example project showing bug results. Two different composite components, one nested inside the other. Second level component shows bound data attribute resolving to NULL during PreRenderViewEvent.
        Hide
        MAtthew Sweeney added a comment -

        Eclipse helios SR2 project.

        Show
        MAtthew Sweeney added a comment - Eclipse helios SR2 project.

          People

          • Assignee:
            Leonardo Uribe
            Reporter:
            MAtthew Sweeney
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development