8 Replies Latest reply on Jun 27, 2008 6:01 PM by obfuscator

    Conversation initial state

    obfuscator

      Hi,


      I have a conceptual problem about setting the initial state of a conversation (passing state from one scope to another unrelated one).


      On the site that I'm designing there is a detailed image page, which optionally can be supplied with a list of other related images. This parameter is useful when watching a set of images. They can depend on context and need to be supplied to the conversation.


      You can open several detailed image conversations, where each conversation holds a related images list.
      The problem I'm having is to understand how to pass this related images list to the conversation initially. The navigation to the detailed image conversation can either be from another conversation or directly from the session, so a nested conversation seems to be the wrong solution.


      I could pass it as a request parameter string, but this  requires a string encoding, decoding and database reload of an already existing collection.


      Another option is passing an object parameter through a GET request with s:link (like in the booking example), but this is essentially equivalent with the previous option, if I understand correctly.


      The naive approach would be to set the list in the session before starting the new conversation and loading from there, but this obviously causes concurrency problems.


      Is there any other (more correct) way of accomplishing the same task? Can I pass objects through JSF somehow? The example I have here can easily be solved through passing id:s at GET parameters, but what if the objects that I want to pass are not available from a central storage?



      Thank you in Advance,


      Alexander

        • 1. Re: Conversation initial state

          Another option is to pass the selected image to the conversation context through outjection (or programmatically) and use a commandLink.  Note that with this approach if you want this entity to be managed in the conversation an EM merge will be required.  Also note that you will lose bookmarkable URLs by relying on POST data here.  Then again, you state that the objects may not be available from a central storage so these drawbacks may not be an issue.


          For example,


          @DataModelSelection
          private Image selectedImage;
          
          @Out(scope=ScopeType.CONVERSATION, required=false)
          private Image image;
          
          ... ...
          
          public String selectImage() {
            image = selectedImage;
          
            return "detailedImageView";
          }



          Once the conversation is promoted to long-running, the image will remain in the context across requests.  Hope it helps.

          • 2. Re: Conversation initial state
            obfuscator

            Thank you for that, It helped solve my problem. I think I understand the semantic difference between nested conversations and normal conversations now. If the same example was made using nested conversations, I believe (as hinted by Red Hat Magazine) that in a nested conversation I could simply do


            class InConversation {
            ...
            
            @DataModelSelection
            private Image selectedImage;
            
            @Out(required=false)
            private Image image;
            
            ... ...
            
            @Begin(nested=true)
            public String selectImage() {
              image = selectedImage;
            
              return "detailedImageView";
            }
            
            }



            , and the image would be automatically outjected to the newly started nested conversation. This is because the new running scope (nested conversation) and parameter outjection scope determined by the bean scope (conversation) are considered semantically equal (both are variable scope conversation). The outjection is therefore determined to end up in the nested one, since the nested conversation was started before the action method call. I think this type of behavior is great for providing initial parameters before, say, a redirect.


            The example you gave works perfectly, but unlike when using nested conversations, all initial parameters need to be explicitly marked to be outjected to the conversation scope (because the outjection scope is not determined by the current scope, but rather (by default) the bean scope), and this time the bean scope is not conversation, it is something different, like EVENT or SESSION, which is considered semantically different to the new running scope (CONVERSATION), and the outjection ends up in the old scope instead.


            This difference in semantics confused me. In the first case, the variable ends up in the new running scope (by default), and in the second it ends up in the old running scope (by default), with the only difference being that nested conversation scope is considered semantically equal to its lower scope (conversation).


            The starting of a nested conversation is therefore very different to normal conversations, and I ask myself if they really need to be? Would it not be useful to simply outject all changed parameters to the new running scope after a scope increase has been encountered (@Begin or equivalent)? This would increase consistency in the framework. There is probably a good reason why not to do so, perhaps this is by design?


            Best Regards
            Alexander

            • 3. Re: Conversation initial state

              The starting of a nested conversation is therefore very different to normal conversations, and I ask myself if they really need to be?

              Not really.  The relationship between a nested conversation and its parent is similar to the relationship between a conversation and the session.  Simply allows state to be segregated when alternate flows are necessary within a conversation.  If you would like a deep understanding of the conversation model and the reasoning behind its design, it is explained in-depth in Chapters 7, 8, and 9 of Seam Framework: Experience the Evolution of Java EE.


              Hope it helps.

              • 4. Re: Conversation initial state
                obfuscator

                I haven't read the book that you are referring too, but quite a lot of documentation and source code, and I'm still feeling like I'm missing something in Seam. Namely that there is no way of passing variables without starting conversations. Consider passing state from one EVENT-scoped bean to another, when the user moves from one page to another.


                If you use redirects, you can (as you mentioned) outject state to the (temporary) conversation, which then can survive a redirect, and you can inject the state in the second bean. You cannot, however, pass state over a GET request boundry. (page 1 -> GET -> page 2). You can use s:link with an action parameter, but that requires a shared context between pages.


                I'm thinking the detail image example here again. Consider an overview page listing all images, and another page showing details of a single image. We want to be able to acheive a split in the state management, passing different images to different detail views. We want to use RESTful URLs, so that both the link from the overview to the detail page, as well as the link shown when on the detail page is bookmarkable. We also want to optimize in the case when we are coming from the overview page, through passing the already loaded image. To acheive this, we can then use conditional loading in the detail view;



                • If the detail page is accessed directly, the entity will be loaded from the db using the supplied page parameter.

                • If the entity was already loaded in the overview page, it is passed along to the detail view, which then uses this entity using a em.merge().



                According to previous reasoning you currently need a conversation to transfer this state between the requests. In fact, you even need to start the conversation before showing the overview view if you want to use GET. In this example there is no natural end to the conversation, so then this conversation just lay around waiting for timeout. Would it therefore not be useful to be able to pass the image between contexts?


                I'm thinking something like:


                //Used with overview.xhtml
                @Name("overview")
                class Overview {
                
                  @DataModel
                  List<Image> images;
                
                  @Factory("images")
                  void loadImages() {
                     images = loadFromDb();
                  }
                  
                
                }
                
                //Used with detail.xhtml
                @Name("detail")
                class Detail {
                 
                  @In(required=false)
                  Image image;
                
                  //page parameter
                  Long imageId;
                
                  void pageLoad() {
                     if (image == null) {
                        image = loadFromDb(imageId);
                     } else {
                        log.debug("Image was passed along");
                        em.merge(image);
                     }
                  }
                }
                



                And the overview.xhtml:


                <h:dataTable value="images" var="i">
                  <s:link view="detail.xhtml">
                    <!-- f:param is not essential, it is simply used to set imageId in the URL to make it
                         bookmarkable -->
                    <f:param name="imageId" value="#{i.id}"/> 
                    <!-- s:pass does the magic of passing state between requests-->
                    <s:pass targetScopeName="image" value="#{i}"/>
                  </s:link>
                </h:dataTable>
                



                Seam could support this by extracting the values from the inital scope before rendering the overview page, store them, give the state transfer a tx number, and then put the state back in the target scope when/if the GET request arrive. Another alternative is to make s:link:s action pass the actual object using the same method as above (instead of just passing the name of the variable).


                The URL could be something like


                detail.xhtml?imageId=4711&stateTxId=234
                


                Each tx would just be a little initial context.


                This could of course also be supported in the same manner as page parameters, with implicit passing and mappings.


                What is won with all this? Well,



                • Instead of getting the forking properties of nested conversation, we get function-calling semantics, and the new scope:s lifetime is not determined by its predecessor as in nested conversations. This is really good if you want to start new conversations in the detail views, and get separate timeout. Otherwise each split just adds to the amount of state that needs to be held for the user, and no branches can ever be removed until the parent conversation ends.

                • We do not move all state to each new page, just the relevant details, reducing clutter.

                • Opening multiple sub-views is not a problem, which it is if you use the shared conversation with s:link approach.



                On the negative side,



                • All the small tx scopes, the initial contexts, still need to be stored with a timeout. However, using above mentioned approach, this can be rather short and cache-based, since page parameters can cover up for cache evictions.



                So we can work around the problem of creating a conversation, make all the links bookmarkable, and still get the benefits of state-passing between contexts.


                Perhaps this is all unnecessary, but I fail to see how this type of behaviour could be acheived using the current features of Seam.


                Thank you for taking time to read all this.
                Best Regards



                Alexander

                • 5. Re: Conversation initial state

                  You're over-complicating things.


                  If you want bookmarks and you want to be able to pass state, simply use request parameters across a redirect and define a param in your detail page definition:


                  ... ...
                  <page view-id="/imageSearch.xhtml">
                    <navigation>
                      <redirect view-id="/imageDetail.xhtml">
                        <param name="imageId" value="#{image.id}"/>
                      </redirect>
                    </navigation>
                  </page>
                  ... ...
                  
                  <page view-id="/imageDetail.xhtml">
                    <param name="imageId" value="#{imageDetailAction.imageId}" />
                    <begin-conversation />
                    ... ...
                  </page>



                  Perform the outjection still, but use a factory method that will be executed on the detail page only when the image is not available already from the conversation context:


                  @Name("imageDetailAction")
                  @Scope(ScopeType.CONVERSATION)
                  public class ImageDetailAction {
                    private Long imageId;
                    
                    ... ...
                  
                    @In(required=false) @Out(required=false)
                    private Image image;
                  
                    @Factory("image")
                    public void loadImage() {
                      image = em.find(Image.class, imageId);
                    }
                  
                    ... ...



                  Now your page is RESTful, can accept state, and will begin the conversation when the page is accessed.  Sorry if there are any syntax errors, coding directly into the input box here.


                  There are many, many ways to do things in Seam (this is a very good thing) so if you're having doubts just continue to ask on the forums.  Good luck.

                  • 6. Re: Conversation initial state
                    obfuscator

                    Yes, I think I am complicating things slightly, but there is a purpose :). Your example gets the job done elegantly, but it does not transfer the actual object from the search page to the detail page, only the id. It makes the link on the detail page bookmarkable, but not the links on the search page.


                    Taking the hotel booking as an example, the problem I am seeing is that the search page is not really a part of the conversation of booking a hotel, and it shouldn't be. Since it is not, it cannot pass the selected hotel object to the start of the booking hotel process, unless you use a POST and a redirect. It would be nice to be able to do that without using POST, to get the links on the search page bookmarkable as well. The combination transfer state and use GET cannot be combined. I think this would be nice :)



                    There are many, many ways to do things in Seam (this is a very good thing) so if you're having doubts just continue to ask on the forums. Good luck.

                    Yep, I am beginning to notice this now. It takes a lot of time to understand the rationale behind everything. Almost like learning a new programming paradigm. I won't argue too much, I would just like someone to tell me that it is either just not implemented or that my idea is dumb :)


                    Cheers

                    • 7. Re: Conversation initial state

                      Your example gets the job done elegantly, but it does not transfer the actual object from the search page to the detail page, only the id.

                      Actually it will if you outject the Image prior to the redirect, but as you state it does not allow the search page to be bookmarked.



                      The combination transfer state and use GET cannot be combined. I think this would be nice :)

                      You can but GET requires that state be defined through the query string.  Either the state must then be pulled from the context (through some magical operations) or initialized based on the request parameters (i.e. factory creation).  Take a look at the URL when an s:link (which initiates a GET) is trying to set the DataModelSelection.  It sends a magic value to determine what value to pull from the DataModel in the context.  There has been consideration on removing this feature as it does not make sense for a GET request given that it is a temporary loop variable.


                      You could take some approach similar to this as your situation is similar, but you are going to have to perform some very messy operations to determine whether to pull from the context or initialize based on the passed param (not recommended).  If there is concern about performance when loading an entity from a GET, perhaps you should consider a caching solution rather than trying to work around the GET.  Seam provides a very elegant multi-layered caching solution that is quite easy to use.


                      Hope it helps.

                      • 8. Re: Conversation initial state
                        obfuscator

                        That sounds reasonable. Now I feel more comfortable about how to decide which method to use. Thank you, and have a nice weekend!