1 2 3 4 Previous Next 46 Replies Latest reply on Jul 19, 2012 5:26 PM by kragoth Go to original post
      • 30. Re: How to end and begin a new conversation with one signle <s:link>
        asookazian

        Ok guys.  I have a potential solution that may need some refactoring/re-design but it does work.  Navigate to http://localhost:8080/foo/TestRestartLRC.seam.  click the startLRC button.  click the ReStartLRC button.  check conversation entries via showConversationEntriesData button or debug.seam.  The @End(beforeRedirect=true) button is there for reference and it does work as well (assuming an LRC is already prior to clicking that button of course).


        I had to use a session-scoped Boolean context variable (restartNow) in this solution but that may be ok considering Seam only allows one active LRC at a time (others are background LRCs in the same session).


        Alternatively, and with less code, you could likely use @Begin and @End annotations rather than the Conversation API code I used for the restartLRC() and restartLRCPart2() methods.


        The problem that the Seam core team has in implementing a solution in their codebase is the fact that a <redirect/> is required for the end LRC and immediate begin LRC to work.  I'm not sure how they would solve that problem and thus that's why PMuir is asking for a wiki to illustrate a use case and/or best practice regarding how to handle this scenario.


        Someone plz try it out with an <a4j:support event="onselect" action="#{foo.bar}" with HtmlSelectOneMenu.  It would be nice to avoid the page refresh but that's inevitable AFAIK due to the redirect in pages.xml for foo.xhtml.  Enjoy!


        TestRestartLRC.xhtml:


        <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                              "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <ui:composition xmlns="http://www.w3.org/1999/xhtml"
             xmlns:s="http://jboss.com/products/seam/taglib"
             xmlns:ui="http://java.sun.com/jsf/facelets"
             xmlns:f="http://java.sun.com/jsf/core"
             xmlns:h="http://java.sun.com/jsf/html"
             xmlns:rich="http://richfaces.org/rich" 
             xmlns:a4j="http://richfaces.org/a4j"
             template="/templates/normal.xhtml">
             
             <ui:define name="body">
        
                       <h:form>
                            <h:commandButton value="startLRC" action="#{testRestartLRC.startLRC}"/>
                            <h:commandButton value="ReStartLRC" action="#{testRestartLRC.restartLRC}"/>                    
                            <h:commandButton value="@End(beforeRedirect=true)" action="#{testRestartLRC.endLRC}"/>
                            <h:commandButton value="showConversationEntriesData" action="#{testRestartLRC.showConversationEntriesData}"/>
                       </h:form>
                       
             </ui:define>
        
        </ui:composition>



        TestRestartLRC (JavaBean):


        /*
         * http://www.seamframework.org/Community/HowToEndAndBeginANewConversationWithOneSignleSlink#comment80901
         * 
         * @End(beforeRedirect=true) for this purpose. No, there is no way to end and start a new conversation without
         *  doing a redirect, and no, we aren't planning to implement such a function in core Seam. Someone should write
         *  a wiki article showing how to do this, with a custom component :-)
         */
        
        @Name("testRestartLRC")
        @Scope(ScopeType.CONVERSATION)
        public class TestRestartLRC {
             
             @Logger private Log log;
             
             @Out(required=false, scope=ScopeType.SESSION)
             private Boolean restartNow;
             
             @Begin(join=true)
             public String startLRC(){
                  return "/TestRestartLRC.xhtml";
             }
             
             public void restartLRC(){
                  Conversation currentConversation = Conversation.instance();
                  if (currentConversation.isLongRunning())
                       log.info("this is a LRC!");
                  else
                       log.info("this is not a LRC!");
                  
                  currentConversation.endBeforeRedirect();
                  
                  //restartNow idea doesn't work because the conversation has been marked to be demoted
                  //to temp and thus the variable is null after redirect in pages.xml for this action method!
                  
                  //so try outjecting to session scope 
                  restartNow = true;
                  
                  //PMuir is basically saying that if you don't do a redirect, the LRC will not be demoted to temp conv...
                  //I have confirmed this is true in the pages.xml snippet for this action method...
                  
             }
             
             public String restartLRCPart2(){
                  Conversation newConversation = new Conversation();
                  newConversation.begin();
                  restartNow = false;
                  return "/TestRestartLRC.xhtml";
             }
             
             @End(beforeRedirect=true)
             public void endLRC(){
                  //noop
             }
             
             public void showConversationEntriesData(){
                  ConversationEntries conversationEntries = ConversationEntries.instance();
                  Collection<ConversationEntry> collection = conversationEntries.getConversationEntries();
                  if (collection != null && collection.size() > 0) {
                       for (ConversationEntry ce : collection){
                            log.info("ce.getDescription() = "+ce.getDescription());
                            log.info("ce.getId() = "+ce.getId());
                            log.info("ce.getViewId() = "+ce.getViewId());
                            
                            //ConversationEntry.destroy() also works!
                            //ce.destroy();
                       }
                  }
                  else
                       log.info("There are no LRCs right now!");
             }
                  
        }



        pages.xml:


        <?xml version="1.0" encoding="UTF-8"?>
        <pages xmlns="http://jboss.com/products/seam/pages"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
        
               no-conversation-view-id="/home.xhtml"
               login-view-id="/login.xhtml">
        
        <!-- From SiA book, pg. 101: The presence of the <redirect/> element in the rule indicates
                                                     that a redirect should be issued prior to rendering the next view, rather than rendering
                                                     the view immediately in the same request, which is the default. A redirect results in a
                                                     new initial request. -->
        
        <page view-id="/TestRestartLRC.xhtml">
                 <description>TestRestartLRC</description>
                 <navigation from-action="#{testRestartLRC.restartLRC}">         
                      <redirect view-id="/TestRestartLRC.xhtml"/>              
                 </navigation>
                 <action execute="#{testRestartLRC.restartLRCPart2}" if="#{restartNow}"/>
            </page>



        Please report any problems/comments.  thx.

        • 31. Re: How to end and begin a new conversation with one signle <s:link>
          kragoth

          The only issue I have with your proposed solution is that it will scale very poorly and more then likely will be a real pain to debug if problems arise. (Don't mind my comments though as I don't really understand your requirements so they could be way off track)


          If you make yourself a NavigationManager or something similar you will be able to pass parameters around start/end conversations whenever you want and do it all in java code. (yeah I hate pages.xml it makes debugging so much harder :P)


          Outjecting to the session scope is well.... dangerous. Especially if your application is free form navigation. You could end up with old state being used when you didn't want it.


          But at the end of the day as long as you find a solution that will work for you that's good. However I would be testing it very thoroughly. Especially re-entry conditions that may have been affected by previous state that was outjected into the session.

          • 32. Re: How to end and begin a new conversation with one signle <s:link>
            joelb

            All:


            I thought I would provide this code that we use to log when Conversations have ended and begun.  This has proven very valuable to us in debugging things.  Have At!



            @Stateless
            @Name(conversationListener)
            public class ConversationListenerBean implements ConversationListener {


                    @Logger
                    private Log log;
                   
                    @In
                    Identity identity;
                   
                    @Observer(value equals org.jboss.seam.endConversation)
                    public void observeConversationEnd() {
                           
                            if (identity not equal null) {
                                    log.debug(CONVERSATION ENDED: User Name: plus identity.getUsername()  plus Session HashCode: plus Session.getInstance().hashCode()
                                                    plus Conversation ID: plus Conversation.instance().getId());
                            }      
                    }


                    @Observer(value equals org.jboss.seam.beginConversation)
                    public void observeConversationStart() {
                            if (identity not equal null) {
                                    log.debug(CONVERSATION BEGINNING: User Name: plus identity.getUsername() plus Session HashCode:   plus Session.getInstance().hashCode()
                                                    plus Conversation ID: plus Conversation.instance().getId());
                            }              
                    }
            }

            • 33. Re: How to end and begin a new conversation with one signle <s:link>
              cash1981

              Here is the above code styled


              @Stateless 
              @Name("conversationListener") 
              public class ConversationListenerBean implements ConversationListener { 
              
              @Logger private Log log; 
              @In Identity identity; 
              
              @Observer("org.jboss.seam.endConversation")
              public void observeConversationEnd() { 
                if (identity != null) { 
                  log.debug("CONVERSATION ENDED: User Name: "+ identity.getUsername() + " Session HashCode: " +    Session.getInstance().hashCode() + Conversation ID: + Conversation.instance().getId()); 
                } 
              } 
              
              @Observer("org.jboss.seam.beginConversation") 
              public void observeConversationStart() { 
                if (identity != null) { 
                  log.debug("CONVERSATION BEGINNING: User Name: "+ identity.getUsername() + " Session HashCode: " +    Session.getInstance().hashCode() + Conversation ID: + Conversation.instance().getId()); 
                } 
              } 
              }
              


              • 34. Re: How to end and begin a new conversation with one signle <s:link>
                allanjun

                Great post guys, one year on, still no support from core seam.


                I'm having the same problem.


                Page1 is running in a LRC and it has a link to page2, when user clicks the link it should end page1 LRC and starts a new LRC for page2.


                Tim's way to end and start conversation is fine, but how do I execute some init logic when starts the new conversation on page2?


                Usually in <s:link/>  I call an action that's where the conversation starts and do some init logic and then return the view id for the new page.


                With <s:link/> or <h:commandLink/> calling conversation manage method, where do I execute my init logic then?


                Actually, if you don't have any logic to execute, you might just use pages.xml approach <end-conversation before-redirect="true"/>, and in <s:link/> just define the view-id as action.


                One way I can think of is to execute my init logic in page action, but that means I have to pass in all the parameters required for init logic as request parameter, which has limitations.


                Any suggestion on how to invoke an action which requires inputs from the current page when starts the new conversation?

                • 35. Re: How to end and begin a new conversation with one signle <s:link>
                  geraldolmr.geraldolmr.yahoo.com

                  I've been facing this problem for a long time. It's an application with menus and every menu option should start a new conversation. It's possible that in the middle of a conversation, the user decides to select the same menu option to start again and in this case the components created in the abandoned conversation should be destroyed and recreated.


                  The problem in leaving the conversation and start a new one is that the components are not recreated properly. If the conversation is left to die, when we created a new one the components are reused. I liked the Tim Evers solution, it worked, but I found one problem. When we end the conversation this way, the clean-up method (the one marked with @Destroy) is not called and, in my case, I use this method to clean some resources used by the component.


                  I use the same paradigm used by Alan Li, my s:link (menu) called a init logic in the component that, in turn, returns a direct outcome string with the view to load. Then I changed the logic to do this: my s:link points to a session-scoped method that receives a viewId as its parameter and do Conversation.instance().endBeforeRedirect(); Manager.instance().redirect(view);


                  In the pages.xml, I put an action execute for this view (it's annoying because I have to put one for each view) pointing to my init code inside the component that begins a conversation and it worked. I think there has to be a better way without this ping-pong code, but it worked for me.


                  And, yes, Seam team should come up with a built in solution to do this. It's a very common use case, I guess.

                  • 36. Re: How to end and begin a new conversation with one signle <s:link>
                    kragoth

                    Hey Geraldo,


                    The solution you are using is exactly why I wrote the navigation manager. Having this ping pong code and an entry in pages.xml for each action is rediculous.


                    I don't have any @Destroy annotated methods in my solution so I didn't notice this particular problem with my solution. However, I'm sure with enough investigation I could work out how to ensure the annotated methods get called. It probably just needs an event raised when destroying the old conversation.


                    I am using my application in exactly the same way where conversations start when a menu item is clicked and they can click the same menu item and get a brand new conversation and the old one gets destroyed. So, in principle the NavManager concept should work it would just need some tweeks to ensure the appropriate events get raised.


                    But, as you rightly pointed out. This is something Seam should provide.


                    Forcing developers down the xml path is not a good practice in the first place.

                    • 37. Re: How to end and begin a new conversation with one signle <s:link>
                      allanjun

                      Tim Evers wrote on May 18, 2010 01:24:




                      But, as you rightly pointed out. This is something Seam should provide.

                      Forcing developers down the xml path is not a good practice in the first place.


                      Hi Tim,


                      Mind to create a JIRA on this again? We will vote it.

                      • 38. Re: How to end and begin a new conversation with one signle <s:link>
                        bigwheels16.email1.jkbff.com

                        I decided to make an event to do this.


                        First I added this to the components.xml file:


                        <event type="restartConversation">
                             <action execute="#{navigationController.restartConversation()}"/>
                         </event>



                        Then I used the code from Tim Evers(I left out the last two lines of his example) and put it in a Seam object.


                        @Name("navigationController")
                        @Scope(ScopeType.CONVERSATION)
                        public class NavigationController implements Serializable{
                             ...
                             public void restartConversation() {
                                  String oldConversationId = Conversation.instance().getId();
                                  Conversation.instance().end(); //Flag this conversation for ending
                                  Conversation.instance().leave(); //Leave this conversation (forces new temporary converation to start
                                  ConversationEntries.instance().removeConversationEntry(oldConversationId); //Remove this conversation completely from the system
                                  Conversation.instance().begin(); //Make our new temporary conversation long running
                             }
                        }
                        



                        Then in my pages.xml instead of

                        <begin-conversation />

                        I do this (when I need to ensure a new conversation):


                        <rule if-outcome="schedules_administration">
                            <raise-event type="restartConversation" />
                            <redirect view-id="/Administration/Commissions/Schedules/list.xhtml" />
                        </rule>
                        



                        I don't know why Seam couldn't have just provided a tag to do this out of the box.

                        • 39. Re: How to end and begin a new conversation with one signle <s:link>
                          daniell

                          Hi!


                          I know - this thread is really old. But I had the same problems and think this one could be useful...


                          I was using the solution which has been quoted several times here (end conversation, leave conversation, removeConversationEntry, ...). But I found out that my application has a memory leak when using this method.


                          The reason was, that the conversation seems not to be cleaned up properly. We're using some EJBs in our Seam-Components and need to call the EJB-component-method which is annotated with @Remove. We do this in the @Destroy-method which gets called by Seam when the conversation ends. But in this case, these methods have not been called and the EJBs were waiting for their timeout.


                          After hours of investigation, I found a solution to start Seam's destroy-mechanism. Since I was relying on Seam internals and I'm not sure whether some Components which should not be removed got removed, I dismissed this solution.


                          Now I make 2 redirects with one click: the first redirect ends the conversation and the second one starts a new LRC.


                          I have a Session-scoped component (named conversationHelper) with the following methods:


                          @End(beforeRedirect=true)
                          public String restartConversation(...)
                          {
                              [...] // store the state to continue in the performAction-method
                              return "doRedirect";
                          }
                          
                          @Begin
                          public String performAction()
                          {
                              [...] // read the attributes stored in restartConversation and do something (like calling an action-method)
                              return result;
                          }
                          



                          In pages.xml i have the following:


                            <page view-id="*">
                              <navigation>
                                <rule if-outcome="doRedirect">
                                  <redirect view-id="/redirect.xhtml" />
                                </rule>
                              </navigation>
                            </page>
                          
                            <page view-id="/redirect.xhtml">
                              <action execute="#{conversationHelper.performAction}"/>
                          
                              <navigation>
                                ...
                              </navigation>
                            </page>
                          

                          • 40. Re: How to end and begin a new conversation with one signle <s:link>
                            ibenjes

                            Hi

                             

                            I've started to use Tim's suggestion as well and it works but there are still some problems.

                             

                            a) back buttons don't work anymore, as the converstions don't exist anymore (you get redirected to what every you specified in no-conversation-view-id).

                            I've implemented a slight 'improvement' by using a queue for past conversations:

                             

                             

                            {code}

                            @Name("conversationManager")

                            @Scope(ScopeType.SESSION)

                            public class ConversationManager {

                             

                             

                                      private ConcurrentLinkedQueue<ConversationEntry> conversationQueue = new ConcurrentLinkedQueue<ConversationEntry>();

                             

                             

                                      private static int MAX_ELEMENTS = 2;

                             

                             

                             

                             

                                      @Logger

                                      private static Log log;

                             

                             

                                      public synchronized boolean addConversation(Conversation conv){

                                                if(conv.isLongRunning()){

                                                          if(MAX_ELEMENTS == 0){

                                                                    conv.end();

                                                                    return true;

                                                          }

                                                          ConversationEntry ce = ConversationEntries.getInstance().getConversationEntry(conv.getId());

                                                          conversationQueue.add(ce);

                                                          log.info("Added conversation #0 to history queue head is #1 ", conv.getId(),conversationQueue.peek().getId());

                                                          while(conversationQueue.size()>MAX_ELEMENTS){

                                                                    ConversationEntry c = conversationQueue.poll();

                                                                    if(c!=null){

                                                                              log.info("Removing conversation #0 from history queue", c.getId());

                                                                              c.end();

                                                                              ConversationEntries.instance().removeConversationEntry(c.getId()); //Remove this conversation completely from the system

                                                                    }

                                                          }

                             

                             

                                                }

                                                return false;

                                      }

                             

                             

                             

                             

                             

                             

                            }

                             

                            public void redirect(String viewId){

                                             String oldConversationId = Conversation.instance().getId();

                                             log.info("Ending and leaving conversation #0",oldConversationId);

                                                boolean remove = conversationManager.addConversation(Conversation.instance());

                                                Conversation.instance().leave(); //Leave this conversation (forces new temporary converation to start

                                                if(remove){

                                                          ConversationEntries.instance().removeConversationEntry(oldConversationId); //Remove this conversation completely from the system

                                                }

                                                Conversation.instance().begin(); //Make our new temporary conversation long running

                                                String newConversationId = Conversation.instance().getId(); //get the new conversation's id

                                                log.info("Redirecting to #0 with cid=#1",viewId,newConversationId);

                                                Manager.instance().redirect(viewId, newConversationId ); //redirect to our new view using our new conversation

                            }

                             

                             

                            {code}

                             

                             

                            That way you have at least a bit of back button support.

                             

                            The other problem I've noticed is although the conversations don't appear in the debug.seam page objects for these conversations are still being held in the HttpSession.

                            I have implemented a 'memory debug' page which shows me the content of the different HttpSessions (similar to what the Seam wiki example does with the WikiHttpSessionManager)

                            Screen Shot 2012-05-23 at 15.08.43.png

                            As you can see objects from the conversations 4, 8 and 11 are still held in the HttpSession while only conversation 11 is still active!

                            Screen Shot 2012-05-23 at 15.12.23.png

                             

                            Seam must be doing something else when it is removing conversations.

                            ConversationEntries.instance().removeConversationEntry(oldConversationId);

                            can't be all or it is not doing what we expect it to do.

                             

                            Btw. I've tried both Tim's way directly and my added queue. The memory leak exists in both cases.

                             

                            Regards

                             

                            Immo

                            • 41. Re: How to end and begin a new conversation with one signle <s:link>
                              kragoth

                              Hi Immo,

                               

                              My solution certainly does not support the "Back" button. Obviously, if you perform a conversation.end() then the back button feature is completely out of the question.

                               

                              However, my "solution" is not used on it's own. As I made mention of in previous posts I have a NavigationManager that handles all the navigation logic. Those lines of code I wrote are purely for ending a conversation and beginning another within a single action.

                               

                              My NavigationManager is now 700+ lines of code, not to mention a number of supporting classes that handle all different kinds of navigation. I also have a queue that manages my own idea of 'nested' conversations which allows me to handle the 'back' operation. However, in our app (a financial application) I specifically do not allow the browser 'back' button to be used. Each page in our application has a back button in order to avoid a lot of the problems that come with a browser back button.

                               

                              Previously I posted my complete NavigationManager code (it is a little bit dated now but, I don't think I've changed *that* much).

                               

                              Have a read of this thread https://community.jboss.org/thread/184507?start=15&tstart=0

                              in there I explain how I do 99.9999% of all navigation in our app. I think that to this day the only exception to this is our login action. (I would have to check though).

                               

                              The idea behind the NavigationManager is that each area of our application (which you will see refered to as modules in my code) is responsible for defining it's own navigation. It defines how you get to that area in the app and what parameters must be passed to it to navigate to the area. The calling code is responsible for how to navigate back to itself. The code is not necessarily "pretty" but, I would take this over any pages.xml any day.

                               

                              Now, with regards to the memory leak.

                               

                              There are a few things to note. My code does not raise the appropriate events for ending a conversation which could be responsible for some of the problems. One day if I ever get time to go back and check this I may find the issue. However, what I have noticed is that in our app eventually the memory does get cleaned up. I have not found out why yet but, what I do know is that over the course of the day the memory used by a session seems to stabalize and we've had no issues with running out of memory. Admittedly we don't have a lot of users (well, not many users that have much more then read only access) so, I don't have absolute proof of this.

                               

                              If you do find anything more about the memory leak please post here so we all can benefit.

                               

                              Thanks,

                              Tim

                              • 42. Re: How to end and begin a new conversation with one signle <s:link>
                                ibenjes

                                Hi,

                                 

                                I've 'fixed' the memory leak with a work around. It is far from ideal as it could break with every new release (well I guess we only expecting one more release for 2.x).

                                 

                                The method below removes the objects for a conversation from the HttpSession (You need to have cleaned up your ConversationEntries before as shown in Tim's solution).

                                I've checked with jmap and the objects do get all cleaned, so there aren't any other references to the objects from that conversation around.

                                 

                                If someone knows a better way on how to trigger Seam's cleanup please share it with us.

                                 

                                 

                                {code}

                                public void removeConversationFromSession(String cid){

                                                    StringBuilder builder = new StringBuilder("org.jboss.seam.CONVERSATION#");

                                                    boolean logFreeing = false;

                                                    long usedBefore = 0;

                                                    if(logFreeing){

                                                              Runtime.getRuntime().gc();

                                                              Runtime.getRuntime().gc();

                                                              usedBefore = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed();

                                                    }

                                                    builder.append(cid).append("$");

                                                    String key = builder.toString();

                                                    Iterator<Entry<String,Object>> iter =  FacesContext.getCurrentInstance().getExternalContext().getSessionMap().entrySet().iterator();

                                                    while(iter.hasNext()){

                                                              Entry<String,Object> entry = iter.next();

                                                              if(entry.getKey().startsWith(key)){

                                  log.info("Removing Entry #0 from HttpSession",entry.getKey());

                                                                        iter.remove();

                                                              }

                                 

                                                    }

                                                    if(logFreeing){

                                                              Runtime.getRuntime().gc();

                                                              Runtime.getRuntime().gc();

                                                              long usedAfter = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed();

                                                              log.info("Freed #0 of memory", MathUtility.humanReadableByteCount(usedBefore-usedAfter, false));

                                                    }

                                          }

                                {code}

                                • 43. Re: How to end and begin a new conversation with one signle <s:link>
                                  kragoth

                                  I would prefer to find a way of doing this that allows Seam to cleanup properly.

                                   

                                  Have you tried something simple like Lifecycle.destroyConversationContext(session, conversationId).

                                  Doing it this way would mean that the appropriate events will fire and the full lifecycle will execute. (Well, this is all theory at the moment )

                                   

                                  Your solution seems a bit drastic and in it's current state would not work for me anyway. Often times I will end a whole bunch of converstations all at once (logically nested by my own code not Seams). However, the root conversation is still running. There is no way I want to accidently destroy something belonging to the current conversation. Admittedly your code could probably be easily modified to handle that but, I still don't like the idea of manually digging around the session map.

                                  • 44. Re: How to end and begin a new conversation with one signle <s:link>
                                    ibenjes

                                    Hi Tim,

                                     

                                    believe me I would prefer to find a solution that allows Seam to do the cleanup as well! But if I've got the choice between a memory leak that could kill the server and cleaning it up 'by hand' I chose the later one. I can see from jmap that the object really are gc'ed so there aren't any references hanging around and Seam doesn't know anything about the conversations anymore either.

                                     

                                    I think I've tried the Lifecycle.destroyConversationContext method (or at least a similar one which was mentioned earlier in this thread) but that didn't do the job. Will try again.