9 Replies Latest reply on Apr 2, 2008 4:15 AM by nathan dennis

    Need Help with Conversions and Contexts!

    Burt Prior Newbie

      Hi Seam Users,


      Our team is developing our first production Seam app using Seam 2.1,EJB3, Hibernate JPA, Facelets, and Oracle.


      After reviewing the Seam docs, we are having one heck of a time applying the concepts of Conversations and Contexts to our 2 simple Stateful EJB's.  It's very similar to the 'booking' example; really just a CRUD app.


      If I could describe what my app is trying to do, perhaps someone could help me with some hints on the correct approach.


      The app is only one page with a clickable dataTable populated with a list of items on the right, and the item properties on the left (such as item name, item date, etc).


      The dataTable is populated correctly on pageload from the Stateful Session scoped bean with @DataModel items. (like booking).


      When I click on any item in the list, i simply want to view that items properties, and optionally edit that input text that has

      value="#{item.name}"



      Additionally, I want to 'Add Item', which simply em.persist(item) a new item, then repopulates the list with the new item, highlighting that new item at the top of the list ready to edit (there is always an 'selectedItem').


      That's really it.


      We are experiencing behaviour that we don't understand that we think is related to the Seam contexts and conversions that we don't understand well, and that we probably don't have set correctly for what we are trying to do.


      For example:


      Question: When we edit the item's name (any item property really) it seem's to 'save' the edit (to the cache?), because when we click on another item, then return to item, we see that change. But if some other user in another browser clicks on that item, they don't see the change.


      Our business logic is that once someone edits that items properties (like the item name), anyone in any session should be able to see that change.


      Interestingly, when we stop the app server, the cache must 'flush' to the database, because we see all the correct changes to the properties. We are not concerned that the database is not updated (yet); that's what the cache is for.  But we do want others to see those changes somehow.


      We think we don't have our @Scope's set correctly for our 2 EJB's for the business logic we want our app to support;


      List all the items.
      Add a new item, then see it in the list immediatly.
      edit any item's property; any other users can see that change immediatly.


      Our EJB's and Entities seem functionally correct, but we don't seem to be in the correct 'conversation' or 'scope', especially for 'edit'.


      We are a bit stuck on this.  We would very appreciative of any help or advice.


      Thanks,
      .Burt






        • 1. Re: Need Help with Conversions and Contexts!
          Clint Popetz Apprentice

          Post some code...it sounds like you have started your long-running conversation with @Begin(flushMode=FlushModeType.MANUAL) and you need to do an explicit em.flush() to propogate your changes from the hibernate cache to the db.  But I can't be sure without seeing an example.


          (But please don't print all your code...just the relevant bits.)

          • 2. Re: Need Help with Conversions and Contexts!
            Burt Prior Newbie

            Hi Clint,


            Thanks very much for the reply.  We are in dire need here.  I think your right.  We are not demarking per our simple business logic correctly in the conversation and scope.


            Just to list my app business logic:


            1. Get a list of items on page load and populate the datatable.
            2. By default, select the first item on the list (there's always an active 'selected item').
            3. When any item is clicked in the datatable, it is now the 'selected item', and optionally edit any of its properties.  Other 'sessions' should see any edits to that selected items properties, once they are 'saved' to cache or db. (em.flush()?)
            4. Add a new Item (item 'name' needed from user for Item ctor), add newly added item to the list; refresh list (just like on page load is fine).


            So I guess it's only CRU of CRUD.


            Here is the relevent code:  There's not much to it.


            (note: 'vendors' equal 'items')


            <h:form>
            <h:dataTable id="vendors" value="#{vendors}" var="_vendor" rendered="#{vendors.rowCount>0}">
            
            <h:column>
            <f:facet name="header">ID</f:facet>
              #{_vendor.id}
            </h:column>
            
            <h:column>
             <f:facet name="header">Name</f:facet>
            <h:commandLink action="#{vendorList.setSelectedVendor(_vendor)}">#{_vendor.name}</h:commandLink>
                                </h:column>
                      
            </h:dataTable>
            </h:form>
            



            Add a brand new Vendor (Item):


                      <h:form>
                         <input type="text" id="newVendorName" name="newVendorName" />  
                           <h:commandLink action="#{vendorList.add}">Add a New Vendor</h:commandLink>
                      </h:form>
            



            My only EJB:


            @Stateful
            @Scope(SESSION)
            @Name("vendorList")
            @TransactionAttribute(REQUIRES_NEW)
            public class VendorListAction implements VendorList, Serializable {
               
               @PersistenceContext(type=EXTENDED)
               private EntityManager em;
                 
               @DataModel
               private List<Vendor> vendors;
                 
               @In(required=false) 
               @Out(required=false)
               @DataModelSelection
               private Vendor vendor;
               
               @Logger 
               private Log log;
              
               public void setSelectedVendor(Vendor selectedVendor) {
                    vendor = em.find(Vendor.class, selectedVendor.getId());
               }
               
               @Factory
               @Observer("vendorConfirmed")
               public void getVendors() {
              
                    vendors = em.createQuery("select v from Vendor v").getResultList();
                    
                    vendor = vendors.get(0);   // set the default 'selectedVendor' so it's highlighted in the list.
                    
               }
             
               public Vendor getVendor() {
                  return vendor;
               }
                    
               public void add() {
                    String newVendorName = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("newVendorName");
                     Vendor newVendor = new Vendor(newVendorName, "bprior");
                    em.persist(newVendor);
                     vendor = newVendor;
                     //getVendors();
                }
               
               @Destroy @Remove
               public void destroy() {}
            
            }
            



            Note there are no build or deploy errors;
            We just can't get our business logic 'in sync' with contexts and conversations.


            Thanks again for your help.
            .Burt


              

            • 3. Re: Need Help with Conversions and Contexts!
              Clint Popetz Apprentice

              There are many things I see in your code.




              • The reason different sessions aren't seeing changes to the entities is because each session is getting its own vendorList, and each vendorList has an extended persistence context that is caching the list of vendors, and that context lives for the duration of the containing session bean, which is the lifetime of the http session in your case, because you annotated them @Scope(SESSION.)  This means you never update the list of vendors from the database after your initial retrieval in your factory method.  (You have a commented out call to getVendors()...but that woludn't work for other sessions, and in fact it wouldn't work even for the session in question, because the flush() of your vendor to your database won't happen until the container commits the transaction when add() returns, so the find() won't find anything new.)  So even though each invocation of add() is getting a transaction, and therefore is causing a flush to the database, the other sessions aren't seeing it, because they aren't updating their list of vendors.  I think you probably want your vendor list to be conversation scoped, which is the default for a SFSB if you don't annotate it with @Scope.  That will yield you more standard CRUD behavior, but you are going to need to understand how to use @Begin and @End in that case.  But that's a big step toward understand and using Seam fully, and well worth it.




              • Pulling the parameter for your new vendor name directly from the parameter map is ok, but you could more easily just bind your input tag to a field of your component, or use a @RequestParameter.




              • Unless you have unusual requirements, @TransactionAttribute(REQUIRES_NEW) isn't probably what you want either.  The default if you leave it unspecified is REQUIRED, which does the right thing in almost all cases.




              • Based on your code,you don't need @In on your @DataModelSelection.  But you don't need @DataModelSelection either, based on how you've coded your link to directly call your setSelectedVendor. 



              I'm going to stop there.  I'd really suggest spending some more time with the examples and the reference documentation to better understand what the various annotations mean and what there standard usage is. 


              • 4. Re: Need Help with Conversions and Contexts!
                Burt Prior Newbie

                Hi Clint,


                I've been taking your advice for the last couple days, and really bearing down Seam's Context and Conversations references, and looking at examples, especially on how they might apply to my app.  Thanks again for your excellent advice.


                I now understand much better your advice from your last post, and have started reworking my SFSB code.


                I was wondering if you could help me with a couple questions.


                One of the things that I'm doing differently in my app, is that the  'Add New Vendor', 'Show list of vendors to select', and 'edit the selected vendors properties' business logic are all on the same page.  That's different than the example apps i've been looking at, that navigate to different pages (conversations).


                For example, after add vendor, the Vendor list is refreshed, and that recently added vendor is selected, with its properties ready to edit.


                I was wondering because this seems to affect how I set the conversations scope for each business logic case Add, Edit, List.


                For example, in my refactored code below, should both my Begin's be nested?


                Should I create a new SFSB called AddVendorBean, and make that Scope(EVENT) with only addVendor method?


                Also, it appears to me now that I need to do an 'application transaction' to ensure the data consistency in my app. (i.e, when i make an edit, every other user can see that edit now). This is my intent with the MANUAL flushMode and em.flush().


                Below is my refactored code:


                @Stateful
                @Scope(CONVERSATION)
                @Name("vendorManager")
                public class VendorManagerBean implements VendorManager, Serializable {
                 
                   @PersistenceContext
                   private EntityManager em;
                     
                   @DataModel
                   private List<Vendor> vendors;
                     
                   //@DataModelSelection
                   //private Vendor vendor;
                   
                   @Out
                   private Vendor activeVendor;
                    
                   @Logger 
                   private Log log;
                
                   /*
                    * set the selected vendor to the one the user clicked on, make sure your looking at the latest
                    * vendor data from the cache/database in the 'sidebar'.
                    */
                   
                   public void setSelectedVendor(Vendor selectedVendor) {
                        activeVendor = em.find(Vendor.class, selectedVendor.getId());
                   }
                  
                   public void getVendors() {
                        vendors = em.createQuery("select v from Vendor v").getResultList();
                   }
                 
                   /*
                    * start a new converstion and seam style application transaction. 
                    */
                   
                   @Begin(flushMode = FlushModeType.MANUAL)
                   public void editVendor( Vendor vendor ) {
                        activeVendor = em.find( Vendor.class, vendor.getId() );
                   }
                    
                   /*
                    * start a new conversation when the user choses to add (create) or edit a vendor.
                    */
                   
                   @Begin(flushMode = FlushModeType.MANUAL)
                   public void addVendor() {
                        
                        log.info("adding a vendor.");
                        String name = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("newVendorName");
                  
                        Vendor vendor = new Vendor(name, "bprior");
                        //em.persist(vendor);
                        saveVendor( vendor );
                 
                   }
                   
                   /*
                    * save a new or updated vendor to the database, and end the conversation.
                    */
                   
                   @End
                   public void saveVendor( Vendor vendor ) {
                        
                        try {
                             if (em.find(Vendor.class, vendor.getId())) != null) {
                                  em.merge( vendor );
                             } else {
                                  em.persist( vendor );
                             }
                             
                        } catch( Exception e ) {
                             e.printStackTrace();
                        }
                        
                        em.flush();
                   
                   }
                   
                   @Destroy @Remove
                   public void destroy() {}
                
                }
                



                I would greatly appreciate any guidance and help.


                Thanks again,
                .Burt

                • 5. Re: Need Help with Conversions and Contexts!
                  Clint Popetz Apprentice

                  Now we're deep enough that I can't really answer that without working through the use cases on your source, and so you're judgment is probably as good as mine, and there is of course more than one way to do it.


                  My own preference is usually to break out the controllers for operations on the child objects (in your case, the vendors) into separate action components, so yes, I would make a separate conversation-scoped action component for editing the vendor, and start a nested conversation for operations on that component.  You are correct, there isn't an example in the stock seam examples for doing listing and CRUD from a single page with ajax and nested conversations, though a search through this forum will yield others trying that same approach.


                  • 6. Re: Need Help with Conversions and Contexts!
                    Burt Prior Newbie

                    Hi Clint,


                    It looks like using 'Seam Remoting' is a more suitable way to implement my 'single-page' CRUD application.


                    I'm not at all sure how it works;  I guess i better get reading on it.


                    I was hoping there were other 'single-page' example apps out there, using Seam Remoting or not, that I could look at and apply to my application.


                    Thanks again for your help,
                    .Burt

                    • 7. Re: Need Help with Conversions and Contexts!
                      nathan dennis Expert

                      if your going the seams remoting route with a single page app... you should definitely take 5 minutes and go read about my recent confusing calamity when mixing conversation context with seams remoting and richfaces ajax calls. it will save you major frustration..


                      http://www.seamframework.org/Community/SeamRemotingAndContext

                      • 8. Re: Need Help with Conversions and Contexts!
                        Burt Prior Newbie

                        Hi Nathan,


                        I just read your post.  Wow, that was amazing how you figured that out.


                        Here is my code:


                        <h:inputText id="vName" value="#{item.name}" >
                        <a4j:support event="onblur" action="#{itemManager.updateItem}" reRender="itemTable" />
                        </h:inputText>
                        



                        Do i just wrap the above (all) with a s:conversationId for every a4j:support I have?


                        Reading about your app, I'm still not sure if I need to go the Seam Remoting route... my single page app is so simple compared to yours.


                        Perhaps I should break out the 'add', 'edit', 'select', 'list' logic out to discrete SFSB's with different scopes.  Hmmm... not sure.


                        Thanks very much for the excellent advice.
                        .Burt

                        • 9. Re: Need Help with Conversions and Contexts!
                          nathan dennis Expert

                          actually to addend the cid to the post you nest the tag inside the  the ajax tag.


                          <h:inputText id="vName" value="#{item.name}" >
                          <a4j:support event="onblur" action="#{itemManager.updateItem}" reRender="itemTable">
                                 <s:conversationId/>
                          <a4j:support> 
                          </h:inputText>
                          


                          i personally havent had trouble with a4j:support loosing the cid, but i wouldn't doubt that it could happen. it definitely will if your using jsFunction