13 Replies Latest reply on Jun 6, 2006 12:09 PM by Minh Vu

    Problem with @DataModel in PAGE scope

    Simon Newbie

      After a @DataModel annotated variable is set up for use in page scope, subsequent calls to the DataModel holding component from a JSF view cause the DataModel to be lost.

      eg:
      1. dataTable refers to @DataModel annotated dataList, @Factory creates the dataList, & ListDataModel wrapped dataList gets bound to page scope
      2. dataTable renders, successfully using any direct bindings to the dataList
      2. "next page" commandLink's render check invokes isNextPage on component to see if further results are available

      Problems are caused at step 2, as (it seems) the last called method determines what will end up in the page scope for any subsequent use of dataList

      Perhaps page scope should be "event then page" scope for outgoing @DataModel. There might also be a wider issue with outjecting variables to the page scope, unless it's expected that a page scoped variable be @In-jected back in prior to method calls.

        • 1. Re: Problem with @DataModel in PAGE scope
          Simon Newbie

          Oops, my numbering is wrong. Problem is caused at the third step

          • 2. Re: Problem with @DataModel in PAGE scope
            Gavin King Master

            Wellyes, the idea is that if you need to keep using it in the next RENDER_RESPONSE phase, you need to re-outject it...

            • 3. Re: Problem with @DataModel in PAGE scope
              Simon Newbie

              The problem happens before the end of the first RENDER_RESPONSE phase is complete. The view is a facelet xhtml with a dataTable & next/previous buttons which check if they need to be rendered. Page scoping is very useful btw. Cheers

              • 4. Re: Problem with @DataModel in PAGE scope
                Gavin King Master

                I don't follow.

                • 5. Re: Problem with @DataModel in PAGE scope
                  Simon Newbie

                  I'll get together an example of what I mean, and post it here later.

                  • 6. Re: Problem with @DataModel in PAGE scope
                    Simon Newbie

                    Hmmmm.....Looks like it happens during APPLY_REQUEST_VALUES, as the isListOK method is invoked. The projectList will be null from that invocation onwards.

                    20:35:55,328 ERROR [STDERR] 27-Mar-2006 20:35:55 DebugPhaseListener beforePhase
                    INFO: before phase: RESTORE_VIEW 1
                    20:35:55,562 ERROR [STDERR] 27-Mar-2006 20:35:55 DebugPhaseListener afterPhase
                    INFO: after phase: RESTORE_VIEW 1
                    20:35:55,562 ERROR [STDERR] 27-Mar-2006 20:35:55 DebugPhaseListener beforePhase
                    INFO: before phase: APPLY_REQUEST_VALUES 2
                    20:35:55,578 ERROR [STDERR] 27-Mar-2006 20:35:55 eurojobs.actions.ProjectFinderBean isListOK
                    INFO: Checking List is OK->null

                    <h:form>
                     <h:dataTable value="#{projectList}" var="project">
                     <h:column>
                     <h:commandButton action="#{testAction.select}" value="#{project.name}" />
                     </h:column>
                     </h:dataTable>
                     <h:commandButton disabled="#{!projectFinder.listOK}" />
                    </h:form>

                    @Name("projectFinder")
                    @Stateful
                    @Scope(ScopeType.EVENT)
                    @Interceptors(SeamInterceptor.class)
                    public class ProjectFinderBean implements ProjectFinder {
                    
                     private int pageSize = 25;
                    
                     @DataModel(scope = ScopeType.PAGE)
                     private List<Job> projectList;
                    
                     @DataModelSelection
                     private Job selectedProject;
                    
                     @PersistenceContext
                     private EntityManager entityManager;
                    
                     @Factory("projectList")
                     public void findProjects() {
                     executeQuery();
                     }
                    
                     private void executeQuery() {
                     projectList = (List<Project>) entityManager.createQuery("from Project order by name")
                     .getResultList();
                     }
                    
                     public void refresh() {
                     if (projectList != null)
                     executeQuery();
                     }
                    
                     public Job getSelection() {
                     return entityManager.merge(selectedProject);
                     }
                    
                     public boolean isListOK() {
                     Logger.global.info("Checking List is OK->" +projectList);
                     return projectList != null && projectList.size() > 0;
                     }
                    
                     @Destroy
                     @Remove
                     public void destroy() {
                     }
                    
                    }


                    • 7. Re: Problem with @DataModel in PAGE scope
                      Simon Newbie

                      So, it seems this is a classic JSF gotcha - backing bean calls are made more than once (to a Seam component in this case). Once for the render, and then during the next web request, to reconstruct the component tree. The component is already destroyed by the time the second call is made, so a new instance of the component is created. This second instance will perform all kinds of bijection & business processing mischief, like killing off the @DataModel

                      For immediate needs, I could outject nextPage/previousPage Booleans so that the component is never referenced from the view for value bindings.

                      As well as this having been a problem for me within Seam, I also see Seam as being ideally placed to solve this more general JSF issue. Would it be possible to extend the life of Seam components to cover the needed calls for component tree rebuilding? Looks like people in the straight managed bean environment are resorting to session scope backing beans, as request won't work and there's few alternatives, but Seam could be more clever.

                      • 8. Re: Problem with @DataModel in PAGE scope
                        Gavin King Master

                        I don't really grok. Can you explain more fully, from the start? Thanks.

                        • 9. Re: Problem with @DataModel in PAGE scope
                          Simon Newbie

                          Hi Gavin,

                          I'll skip over/ignore some aspects (like capturing user typed form updates & actions), and concentrate on the core issue. I'll also try to keep it simple, for the benefit of anybody else reading. Whilst I'm talking here about Seam of course, I'd like to be clear to people that it's not a problem with Seam - rather something Seam can feel the impact of & also help overcome.

                          For a component like this in a view:

                          <h:commandButton disabled="#{!projectFinder.listOK}" />

                          JSF uses the #{!projectFinder.listOK} binding twice. Once during the RENDER_RESPONSE phase for determining component render style, then again when JSF rebuilds the component tree. This enables JSF to restore the view then generate a detected event, as if the UI was sitting in memory the whole time. It happens during APPLY_REQUEST_VALUES, where a call is made to UIViewRoot.processDecodes.

                          Here's the JSF1.2-b16 implementation call stack from the second use of the value binding. I believe from looking at the MyFaces source, that the same would happen there:

                          $Proxy97.isListOK() line: not available
                          NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
                          NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
                          DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
                          Method.invoke(Object, Object...) line: 585
                          BeanELResolver.getValue(ELContext, Object, Object) line: 218
                          FacesCompositeELResolver(CompositeELResolver).getValue(ELContext, Object, Object) line: 135
                          FacesCompositeELResolver.getValue(ELContext, Object, Object) line: 58
                          AstValue.getValue(EvaluationContext) line: 96
                          AstDeferredExpression.getValue(EvaluationContext) line: 25
                          AstCompositeExpression.getValue(EvaluationContext) line: 30
                          ValueExpressionImpl.getValue(ELContext) line: 183
                          TagValueExpression.getValue(ELContext) line: 71
                          HtmlCommandButton.isDisabled() line: 169
                          NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
                          NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
                          DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
                          Method.invoke(Object, Object...) line: 585
                          UIComponentBase$AttributesMap.get(Object) line: 1525
                          Util.componentIsDisabledOrReadonly(UIComponent) line: 572
                          ButtonRenderer.decode(FacesContext, UIComponent) line: 108
                          HtmlCommandButton(UIComponentBase).decode(FacesContext) line: 791
                          HtmlCommandButton(UIComponentBase).processDecodes(FacesContext) line: 1012
                          HtmlForm(UIForm).processDecodes(FacesContext) line: 203
                          UIViewRoot(UIComponentBase).processDecodes(FacesContext) line: 1007
                          UIViewRoot.processDecodes(FacesContext) line: 499
                          ApplyRequestValuesPhase.execute(FacesContext) line: 101

                          The end result for a JSF app is that the #{!projectFinder.listOK} binding referenced on "viewX" must be available during the handling of any action event generated from "viewX". Because "projectFinder" was destroyed at the end of the last web request, a new instance of "projectFinder" will be created during the evaluation of the #{!projectFinder.listOK} binding.

                          This new instance will be a default initialized bean (or component in the case of Seam), which leads to an invalid restoration of view state. In the example of projectFinder in this thread, which is from the issue example (plus isListOK method), the projectList will be set to null - and then be outjected over the top of the projectList value we want to keep.

                          What many JSF developers seem to be after (a hunch), is a way to keep their backing beans around to service the binding evaluations during view reconstruction. It seems a lot are resorting to session scope (but will then have trouble if they need multiple instances of course). Seam might be able to get round this with style.

                          The quick-fix solution, as I posted here, is to outject a listOK Boolean to page scope, and change view references to #{listOK}. Because it's page bound, the view reconstruction will be able to get it's value despite the component that created it being destroyed. Also, avoid heavy business logic in bound method - do that in the action & set up info that getters can access simply from other lifecycle phases.

                          It would be great though if Seam components themselves could be destroyed later, so that the component tree can be rebuilt from the correct instance. There would be a cost to holding/tracking the instance, but savings also. For example, projectList might be built again from db after it gets nulled out, even though we end up with an invalid state for the new projectFinder instance.

                          The "component lifetime & usage lifetime" mismatch is not just for EVENT scope, but for other scopes too. Page fortunately offers a way around it. I'm new to a lot of the tech in this stack, and it would be good to be told I'm wrong about this stuff, but I don't think so. Thoughts?

                          • 10. Re: Problem with @DataModel in PAGE scope
                            Gavin King Master

                            I was thinking "page scope!", until I read far enough where you said that is what you are doing :-)

                            I reckon that's a good solution, actually....

                            • 11. Re: Problem with @DataModel in PAGE scope
                              Simon Newbie

                              The problem as I see it, unfortunately, exists will all non page scope referenced components. There is in fact no way to reliably value bind to a Seam component from within the view that will accompany that component's destruction, due to lifecycle differences.

                              For example, when a Seam conversation finishes & it's conversation scoped action component is destroyed, again I'd need to outject page objects for any value bindings in the post-conversational view. Provided that mid-conversational views reference bi-jected objects (instead of via getters on the action component), I could sneakily outject a same-named page scoped copy at the end of the conversation (hence binding el will be transparently consistent for both mid & post-conversational bindings).

                              Perhaps that would work as a fix, but it would be much better to avoid this issue in the first place. People have walked away from JSF over this (standard backing beans have no hope of matching JSF's lifecycle), and I really think Seam could address the problem as a core value proposition.

                              What I'm suggesting is that ways to match the component lifecycle more closely to JSF's are looked at. In the contexts.PageContext code, it recognises that the event boundary is in a phase unrelated to that supposed by a web request centric view - and I think PageContext is on to something generally true with JSF. I don't doubt that this is tricky, but I urge some serious thought on the matter. I think you and other key people work incredibly hard, so I hate to burden you, but I do think that the problem needs to be understood & discussed at a Seam project level.

                              Anyway, ciao for now. Cheers

                              • 12. @DataModel in Page Scope just not work
                                Minh Vu Newbie

                                1. I used the pagescope for datamodel to avoid caching. It work well, the factory method got called every time I visit the page.

                                2. The problem is when I add another input field to the page, then the datamodelselectionindex never get updated (always 0)

                                ????

                                • 13. Re: Problem with @DataModel in PAGE scope
                                  Minh Vu Newbie

                                  I want to make something clear:

                                  I tried to add the inputfield, the datamodelseletionindex is not updated. Then I remove the input field, the datamodelselectionindex is always 0.

                                  The only different is the setmethod of the input field get called before the select method (associated with the button on every row) get called. The factory method is not even got called so the list did not got reset.

                                  What's going on? Gavin

                                  Sorry I don't have much time to read the source code.