Tapestry
  1. Tapestry
  2. TAPESTRY-2460

Nested BeanEditors (where the block parameter for a property to one BeanEditor contains another BeanEditor) results in a StackOverflowException

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 5.0.13
    • Fix Version/s: 5.0.15
    • Component/s: tapestry-core
    • Labels:
      None

      Description

      the eclipse/maven example project shows how a StackOverflowException is thrown if a Form is submitted that contains a BeanEditor that itself contains another BeanEditor. However the initial display works perfectly.

      somehow i suspect the PropertyConduits to be responsible because two of them produce the infinite loop.

      1. beaneditor.zip
        11 kB
        Kristian Marinkovic

        Activity

        Hide
        Howard M. Lewis Ship added a comment -

        Ran your application using 5.0.15 and it worked no problems.

        Show
        Howard M. Lewis Ship added a comment - Ran your application using 5.0.15 and it worked no problems.
        Hide
        Howard M. Lewis Ship added a comment -

        This jumped out at me:

        @Component(parameters =

        { "object=context.propertyValue", "model=model" }

        )
        private BeanEditor editor2;

        I suspect things would work if

        object=a.data

        I think what's happening is that the inner BeanEditor is trying to update a property of its object ... that means that a Block is, via the PropertyEditContext, trying to resolve context.propertyValue.

        i.e. this code:

        public void setPropertyValue(Object value)

        { propertyModel.getConduit().set(object, value); }

        In other words, we have, say, a TextField whose value is bound to context.propertyValue.

        However, to update that we need the object parameter bound to BeanEditor and passed to PropertyEditor.

        That object parameter is bound to .... context.propertyValue.

        The reason it works for rendering, and not for form submission, is that during rendering, the parameter fields (object inside PropertyEditor and BeanEditor) cache their values. During a form submssion, the value are re-resolved on each reference, and because they are @Environmental, the same object is used at both levels, and that leads to the endless loop.

        I'm thinking about how I can recognize and fix this case. I think BeanEditor needs to add Form actions to resolve the object parameter and store its value into a temporary field; that is, dereference the object parmater just once, around any usages of PropertyEditor.

        Show
        Howard M. Lewis Ship added a comment - This jumped out at me: @Component(parameters = { "object=context.propertyValue", "model=model" } ) private BeanEditor editor2; I suspect things would work if object=a.data I think what's happening is that the inner BeanEditor is trying to update a property of its object ... that means that a Block is, via the PropertyEditContext, trying to resolve context.propertyValue. i.e. this code: public void setPropertyValue(Object value) { propertyModel.getConduit().set(object, value); } In other words, we have, say, a TextField whose value is bound to context.propertyValue. However, to update that we need the object parameter bound to BeanEditor and passed to PropertyEditor. That object parameter is bound to .... context.propertyValue. The reason it works for rendering, and not for form submission, is that during rendering, the parameter fields (object inside PropertyEditor and BeanEditor) cache their values. During a form submssion, the value are re-resolved on each reference, and because they are @Environmental, the same object is used at both levels, and that leads to the endless loop. I'm thinking about how I can recognize and fix this case. I think BeanEditor needs to add Form actions to resolve the object parameter and store its value into a temporary field; that is, dereference the object parmater just once, around any usages of PropertyEditor.
        Hide
        Martin Papy added a comment -

        Hi Kristian,

        The only workaround I found, is the way I did it in the first place :

        In AppModule :

        public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class<?>, String> configuration)

        { configuration.add(Address.class, "address"); }

        In tml file :

        <t:beanEditForm t:object="user">
        <t:parameter name="address">
        <t:beanEditor t:object="user.address" />
        </t:parameter>
        </t:beanEditForm>

        I hoped it would be corrected for T5.0.14, but it seems it won't be the case.

        Show
        Martin Papy added a comment - Hi Kristian, The only workaround I found, is the way I did it in the first place : In AppModule : public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class<?>, String> configuration) { configuration.add(Address.class, "address"); } In tml file : <t:beanEditForm t:object="user"> <t:parameter name="address"> <t:beanEditor t:object="user.address" /> </t:parameter> </t:beanEditForm> I hoped it would be corrected for T5.0.14, but it seems it won't be the case.
        Hide
        Kristian Marinkovic added a comment -

        hi Martin,

        i'm glad someone else experiences the same problem

        have you found a workaround?

        g,
        kris

        Show
        Kristian Marinkovic added a comment - hi Martin, i'm glad someone else experiences the same problem have you found a workaround? g, kris
        Hide
        Martin Papy added a comment -

        Hi Howard,

        I have also the same problem. I use a BeanEditor for editing a 'complex' property by contributing to BeanBlockSource. The display goes well, but the update throw the StackOverflowError like Kristian.

        BTW I did'nt had the error when putting directly the BeanEditor in the BeanEditForm. It occured only when I wanted to use the BeanBlockSource

        For the contribution I followed exactly the exemple given in "Adding New Property Editors" the doc : http://tapestry.formos.com/nightly/tapestry5/tapestry-core/guide/beaneditform.html.

        in AppPropertyEditBlocks.java

        @Environmental
        private PropertyEditContext _context;

        @Component(parameters =

        { "object=address" }

        ) // We need to cast from Object to Address to make the BeanEditor work
        private BeanEditor _addressEditor;

        public Address getAddress()

        { return ((Address)getContext().getPropertyValue()); }

        in AppPropertyEditBlocks.tml

        <t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
        <t:block id="address">
        <fieldset>
        <legend align="top">$

        {message:address-legend}

        </legend>
        <t:beanEditor t:id="addressEditor" />
        </fieldset>
        </t:block>
        </t:container>

        Show
        Martin Papy added a comment - Hi Howard, I have also the same problem. I use a BeanEditor for editing a 'complex' property by contributing to BeanBlockSource. The display goes well, but the update throw the StackOverflowError like Kristian. BTW I did'nt had the error when putting directly the BeanEditor in the BeanEditForm. It occured only when I wanted to use the BeanBlockSource For the contribution I followed exactly the exemple given in "Adding New Property Editors" the doc : http://tapestry.formos.com/nightly/tapestry5/tapestry-core/guide/beaneditform.html . in AppPropertyEditBlocks.java @Environmental private PropertyEditContext _context; @Component(parameters = { "object=address" } ) // We need to cast from Object to Address to make the BeanEditor work private BeanEditor _addressEditor; public Address getAddress() { return ((Address)getContext().getPropertyValue()); } in AppPropertyEditBlocks.tml <t:container xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> <t:block id="address"> <fieldset> <legend align="top">$ {message:address-legend} </legend> <t:beanEditor t:id="addressEditor" /> </fieldset> </t:block> </t:container>
        Hide
        Kristian Marinkovic added a comment - - edited

        hi howard,

        this problem is still present in the latest svn version (678315)

        Show
        Kristian Marinkovic added a comment - - edited hi howard, this problem is still present in the latest svn version (678315)
        Hide
        Kristian Marinkovic added a comment - - edited

        hi howard,

        i tested my beaneditor example with the latest codebase (svn 670759 and 670748) and i still got the same StackOverflowException.

        My test tries to simulate an example where the second BeanEditor receives its data from a PropertyEditContext object. it cannot access the child property directly because the block is defined in another page that contributes the required Blocks.

        i was not able to track down the problem myself, but i guess the problem is that the first BeanEditors' PropertyEditor passes the object to the PropertyEditContext by creating a conduit. Then the second BeanEditor tries to pass in the object as well by creating another conduit. and somehow the first conduit starts pointing to the second conduit (or the other way round). when i then submit the form the BeanEditor will try to read the object parameter by reading the conduit and thus starting the loop.

        As said, its just a guess. I already took a look at the cache inside the PropertyConduitSource but couldn't find anything. below is the stack trace i get

        g,
        kris

        // the beginning of the stack trace is different

        1. rg.apache.tapestry5.ioc.util.CaseInsensitiveMap.caseInsenitiveHashCode(CaseInsensitiveMap.java:485)
        2. org.apache.tapestry5.ioc.util.CaseInsensitiveMap.select(CaseInsensitiveMap.java:390)
        3. org.apache.tapestry5.ioc.util.CaseInsensitiveMap.get(CaseInsensitiveMap.java:367)
        4. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.getBinding(InternalComponentResourcesImpl.java:284)
        5. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.isBound(InternalComponentResourcesImpl.java:158)
        6. org.apache.tapestry5.corelib.components.BeanEditor._$read_parameter_object(BeanEditor.java)
        7. org.apache.tapestry5.corelib.components.BeanEditor.getObject(BeanEditor.java:142)
        8. org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53)
        9. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:237)
        10. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:252)
        11. org.apache.tapestry5.corelib.components.PropertyEditor._$read_parameter_object(PropertyEditor.java)
        12. org.apache.tapestry5.corelib.components.PropertyEditor.access$2(PropertyEditor.java:86)
        13. org.apache.tapestry5.corelib.components.PropertyEditor$1.getPropertyValue(PropertyEditor.java:166)
        14. org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53)
        15. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:237)
        16. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:252)
        17. org.apache.tapestry5.corelib.components.BeanEditor._$read_parameter_object(BeanEditor.java)
        18. org.apache.tapestry5.corelib.components.BeanEditor.getObject(BeanEditor.java:142)
        19. org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53)
        20. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:237)
        21. org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:252)
        22. org.apache.tapestry5.corelib.components.PropertyEditor._$read_parameter_object(PropertyEditor.java)
        23. org.apache.tapestry5.corelib.components.PropertyEditor.access$2(PropertyEditor.java:86)
        24. org.apache.tapestry5.corelib.components.PropertyEditor$1.getPropertyValue(PropertyEditor.java:166)
        25. org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53)
        Show
        Kristian Marinkovic added a comment - - edited hi howard, i tested my beaneditor example with the latest codebase (svn 670759 and 670748) and i still got the same StackOverflowException. My test tries to simulate an example where the second BeanEditor receives its data from a PropertyEditContext object. it cannot access the child property directly because the block is defined in another page that contributes the required Blocks. i was not able to track down the problem myself, but i guess the problem is that the first BeanEditors' PropertyEditor passes the object to the PropertyEditContext by creating a conduit. Then the second BeanEditor tries to pass in the object as well by creating another conduit. and somehow the first conduit starts pointing to the second conduit (or the other way round). when i then submit the form the BeanEditor will try to read the object parameter by reading the conduit and thus starting the loop. As said, its just a guess. I already took a look at the cache inside the PropertyConduitSource but couldn't find anything. below is the stack trace i get g, kris // the beginning of the stack trace is different rg.apache.tapestry5.ioc.util.CaseInsensitiveMap.caseInsenitiveHashCode(CaseInsensitiveMap.java:485) org.apache.tapestry5.ioc.util.CaseInsensitiveMap.select(CaseInsensitiveMap.java:390) org.apache.tapestry5.ioc.util.CaseInsensitiveMap.get(CaseInsensitiveMap.java:367) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.getBinding(InternalComponentResourcesImpl.java:284) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.isBound(InternalComponentResourcesImpl.java:158) org.apache.tapestry5.corelib.components.BeanEditor._$read_parameter_object(BeanEditor.java) org.apache.tapestry5.corelib.components.BeanEditor.getObject(BeanEditor.java:142) org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:237) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:252) org.apache.tapestry5.corelib.components.PropertyEditor._$read_parameter_object(PropertyEditor.java) org.apache.tapestry5.corelib.components.PropertyEditor.access$2(PropertyEditor.java:86) org.apache.tapestry5.corelib.components.PropertyEditor$1.getPropertyValue(PropertyEditor.java:166) org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:237) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:252) org.apache.tapestry5.corelib.components.BeanEditor._$read_parameter_object(BeanEditor.java) org.apache.tapestry5.corelib.components.BeanEditor.getObject(BeanEditor.java:142) org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:237) org.apache.tapestry5.internal.structure.InternalComponentResourcesImpl.readParameter(InternalComponentResourcesImpl.java:252) org.apache.tapestry5.corelib.components.PropertyEditor._$read_parameter_object(PropertyEditor.java) org.apache.tapestry5.corelib.components.PropertyEditor.access$2(PropertyEditor.java:86) org.apache.tapestry5.corelib.components.PropertyEditor$1.getPropertyValue(PropertyEditor.java:166) org.apache.tapestry5.internal.bindings.PropBinding.get(PropBinding.java:53)
        Hide
        Howard M. Lewis Ship added a comment -

        I'm not certain my test case perfectly reflects your test case, so please re-test and re-open if there's a problem.

        Show
        Howard M. Lewis Ship added a comment - I'm not certain my test case perfectly reflects your test case, so please re-test and re-open if there's a problem.
        Hide
        Howard M. Lewis Ship added a comment -

        Going around in circles for a while, until I uncoverred TAPESTRY-2471.

        Show
        Howard M. Lewis Ship added a comment - Going around in circles for a while, until I uncoverred TAPESTRY-2471 .
        Hide
        Howard M. Lewis Ship added a comment -

        Ok, now with template:

        <t:beaneditform object="parent" add="child">

        <t:parameter name="child">

        <div class="t-beaneditor" style="margin-left: 45px;">
        <h2>Child</h2>
        <t:beaneditor object="parent.child"/>
        </div>
        </t:parameter>

        </t:beaneditform>

        The page renders well, but gives error on submit:

        java.lang.RuntimeException
        Bean editor model for org.apache.tapestry5.integration.app1.data.Person already contains a property model for property 'child'.

        Stack trace

        • org.apache.tapestry5.internal.beaneditor.BeanModelImpl.validateNewPropertyName(BeanModelImpl.java:87)
        • org.apache.tapestry5.internal.beaneditor.BeanModelImpl.add(BeanModelImpl.java:128)
        • org.apache.tapestry5.internal.beaneditor.BeanModelUtils.add(BeanModelUtils.java:59)
        • org.apache.tapestry5.internal.beaneditor.BeanModelUtils.modify(BeanModelUtils.java:39)
        • org.apache.tapestry5.corelib.components.BeanEditForm.onPrepareFromForm(BeanEditForm.java:155)
        Show
        Howard M. Lewis Ship added a comment - Ok, now with template: <t:beaneditform object="parent" add="child"> <t:parameter name="child"> <div class="t-beaneditor" style="margin-left: 45px;"> <h2>Child</h2> <t:beaneditor object="parent.child"/> </div> </t:parameter> </t:beaneditform> The page renders well, but gives error on submit: java.lang.RuntimeException Bean editor model for org.apache.tapestry5.integration.app1.data.Person already contains a property model for property 'child'. Stack trace org.apache.tapestry5.internal.beaneditor.BeanModelImpl.validateNewPropertyName(BeanModelImpl.java:87) org.apache.tapestry5.internal.beaneditor.BeanModelImpl.add(BeanModelImpl.java:128) org.apache.tapestry5.internal.beaneditor.BeanModelUtils.add(BeanModelUtils.java:59) org.apache.tapestry5.internal.beaneditor.BeanModelUtils.modify(BeanModelUtils.java:39) org.apache.tapestry5.corelib.components.BeanEditForm.onPrepareFromForm(BeanEditForm.java:155)
        Hide
        Howard M. Lewis Ship added a comment -

        I'm getting slightly diffrerent results with my test, an exception when rendering:

        java.lang.RuntimeException
        There is no defined way to edit data of type 'null'. Make a contribution to the BeanBlockSource service for this type.

        Stack trace

        • org.apache.tapestry5.internal.services.BeanBlockSourceImpl.getEditBlock(BeanBlockSourceImpl.java:64)
        • org.apache.tapestry5.corelib.components.PropertyEditor.beginRender(PropertyEditor.java:236)
        • org.apache.tapestry5.corelib.components.PropertyEditor.beginRender(PropertyEditor.java)

        From this template:

        <t:beaneditform object="parent" add="child">

        <parameter name="child">
        <t:beaneditor object="parent.child"/>
        </parameter>

        </t:beaneditform>

        (I added the add parameter to BeanEditor and BeanEditForm).

        Show
        Howard M. Lewis Ship added a comment - I'm getting slightly diffrerent results with my test, an exception when rendering: java.lang.RuntimeException There is no defined way to edit data of type 'null'. Make a contribution to the BeanBlockSource service for this type. Stack trace org.apache.tapestry5.internal.services.BeanBlockSourceImpl.getEditBlock(BeanBlockSourceImpl.java:64) org.apache.tapestry5.corelib.components.PropertyEditor.beginRender(PropertyEditor.java:236) org.apache.tapestry5.corelib.components.PropertyEditor.beginRender(PropertyEditor.java) From this template: <t:beaneditform object="parent" add="child"> <parameter name="child"> <t:beaneditor object="parent.child"/> </parameter> </t:beaneditform> (I added the add parameter to BeanEditor and BeanEditForm).
        Hide
        Kristian Marinkovic added a comment -

        removed fix version

        Show
        Kristian Marinkovic added a comment - removed fix version
        Hide
        Kristian Marinkovic added a comment -

        example project with BeanEditor containing another BeanEditor

        Show
        Kristian Marinkovic added a comment - example project with BeanEditor containing another BeanEditor

          People

          • Assignee:
            Howard M. Lewis Ship
            Reporter:
            Kristian Marinkovic
          • Votes:
            2 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development