8 Replies Latest reply on Feb 16, 2010 10:17 AM by iwannugraha

    How to do @Asyncronous with Ajax and Conversations pattern?

      Hi,


      I'm trying to do what I'm sure has been done many times before..



      How do you get a partial data update from an asynchronous process / thread back into the context of the calling long running conversation?

      Specifically:



      1. Main thread is running in the context of a long running conversation

      2. Asynchronous thread needs to interact with the db and needs access to data in that conversation

      3. The asynchronous thread needs to apply partial updates to data in the conversation in the main thread

      4. The ajax / seam remoting poll picks up the partial updates and displays on the screen



      Can anyone give me some pointers on the best approach / proven pattern on how to do this. From reading forums and doco, this is what I've gathered so far:


      @Asynchronous
      @Transactional
      public void someProcessingWork(Object myDataRequiredFromConversation) {
          // long running work done here, including db inserts / updates and
          // creation of a data that ultimately needs to be put (partial data updates) 
          // in the calling conversation context (if still active)
      }
      
      @WebRemote
      public Object getLatestUpdates() {
          // checks for any new partial updates from asynchronous process and 
          // returns delta to UI
      }
      



      How does one bridge the gap between these two threads and the conversation context?


      Thanks


      Troy




        • 1. Re: How to do @Asyncronous with Ajax and Conversations pattern?
          asookazian

          What's your use case?


          Generally, using a4j: or rich: tags, you do not need to use Seam Remoting (which is basically invoking a Seam component method from a javascript function).


          There are many examples of what you are asking in the Seam distro examples.  For example, if you want to dynamically reRender a section of your JSF page (or say just one control, to keep it simple), then you can embed <a4j:support> inside <h:selectOneMenu></h:selectOneMenu> (drop down control) and fire an action method onchange event.


          Specify an id in the reRender attribute for a4j:support which will cause a particular section/region or component (inputText or dataTable, etc.) to be re-rendered on post back of the JSF form submission.


          So in this case, when you select a new value in the drop-down, the dataTable or outputText control re-renders.


          There are some examples of this in the RichFaces live demo site...


          check out the booking example in the Seam distro...

          • 2. Re: How to do @Asyncronous with Ajax and Conversations pattern?
            asookazian

            Here's a good example from main.xhtml in booking example project:


            <div class="section">
              
                <span class="errors">
                   <h:messages id="messages" globalOnly="true"/>
                </span>
                
                <h1>Search Hotels</h1>
            
                 <h:form id="searchCriteria">
                 <fieldset> 
                    <h:inputText id="searchString" value="#{hotelSearch.searchString}" style="width: 165px;">
                     <a:support id="onkeyup" event="onkeyup" actionListener="#{hotelSearch.find}" reRender="searchResults" />
                   </h:inputText>
                   &#160;
                    <a:commandButton id="findHotels" value="Find Hotels" action="#{hotelSearch.find}" reRender="searchResults"/>
                   &#160;
                   <a:status id="status">
                      <f:facet id="StartStatus" name="start">
                         <h:graphicImage id="SpinnerGif" value="/img/spinner.gif"/>
                      </f:facet>
                   </a:status>
                    <br/>
                   <h:outputLabel id="MaximumResultsLabel" for="pageSize">Maximum results:</h:outputLabel>&#160;
                   <h:selectOneMenu id="pageSize" value="#{hotelSearch.pageSize}">
                      <f:selectItem id="PageSize5" itemLabel="5" itemValue="5"/>
                      <f:selectItem id="PageSize10" itemLabel="10" itemValue="10"/>
                      <f:selectItem id="PageSize20" itemLabel="20" itemValue="20"/>
                   </h:selectOneMenu>
                </fieldset>
                </h:form>
                
            </div>
            
            <a:outputPanel id="searchResults">
              <div class="section">
                 <h:outputText id="NoHotelsFoundMessage" value="No Hotels Found" rendered="#{hotels != null and hotels.rowCount==0}"/>
                 <h:dataTable id="hotels" value="#{hotels}" var="hot" rendered="#{hotels.rowCount>0}">
                      <h:column id="column1">
                           <f:facet id="NameFacet" name="header">Name</f:facet>
                           #{hot.name}
                      </h:column>
                      <h:column id="column2">
                           <f:facet id="AddressFacet" name="header">Address</f:facet>
                           #{hot.address}
                      </h:column>
                      <h:column id="column3">
                           <f:facet id="CityStateFacet" name="header">City, State</f:facet>
                           #{hot.city}, #{hot.state}, #{hot.country}
                      </h:column> 
                      <h:column id="column4">
                           <f:facet id="ZipFacet" name="header">Zip</f:facet>
                           #{hot.zip}
                      </h:column>
                      <h:column id="column5">
                           <f:facet id="ActionFacet" name="header">Action</f:facet>
                           <s:link id="viewHotel" value="View Hotel" action="#{hotelBooking.selectHotel(hot)}"/>
                      </h:column>
                 </h:dataTable>
                 <s:link id="MoreResultsLink" value="More results" action="#{hotelSearch.nextPage}" rendered="#{hotelSearch.nextPageAvailable}"/>
              </div>
            </a:outputPanel>



            Note this line in particular:


            <a:support id="onkeyup" event="onkeyup" actionListener="#{hotelSearch.find}" reRender="searchResults" />



            So the a:support (or a4j:support, depending on xmlns config) is embedded inside a h:inputText control.  So when the user types a key in this inputText, the find() method is invoked (actually when the key is released, to be precise).  After the method is invoked, the component in this xhtml/JSF with searchResults (an outputPanel which is basically a container for other UI components) as the id is re-rendered.  From a developer's perspective, and without diving into technical life cycle and implementation details of JSF/RF, etc., this is how partial re-render AJAX works with A4J/RF framework and JSF.  Note that a4j:support does submit the form and all form values in your JSF (which is a HTTP POST request).  It is possible to only submit that particle control's value if you use the ajaxSingle=true attribute setting for a4j:support.


            As far as conversations are concerned, that's a separate topic.  There are temporary and long-running conversations (LRCs) in Seam and they may be started in different ways.  I suggest you read the ref doc and SiA book.  But typically you use @Begin(join=true) to start a LRC and @End to end a LRC.  Both of those annotations are used at the method level only.  Conversations are typically used to model use cases that span multiple request/response cycles (like a shopping cart or wizard).


            I highly recommend you read SiA or the Yuan, 2nd. ed. book and intensely study the booking example prior to proceeding.


            The EE 5 and Seam stack is very intimidating at first if you are new to most of this technology and APIs/concepts...


            HTH


            • 3. Re: How to do @Asyncronous with Ajax and Conversations pattern?

              Hi Arbi,


              Thanks for your reply, however the key thing I'm looking for is the asynchronous thread getting its results back to the main conversation thread that invoked it. I've read some posts about using the Application Scope as global storage and have the (detached from conversation) asynchronous thread put data in the Application Scope so the main conversation thread can also access it.


              Just wondering if this was the best / only pattern??? as is raises other issues like how do you ensure the Application Scoped data is 'cleaned up' when the conversations ends or expires etc...


              Troy


              • 4. Re: How to do @Asyncronous with Ajax and Conversations pattern?
                asookazian

                I have no clue what your project/use-case is.  But have you looked at a4j:poll?


                I'm assuming you want to use @Asynchronous so that the client thread is not blocked for a long-running process (i.e. he does not have to wait for the method to complete processing).


                And when you say main thread does that mean the synchronous thread?


                Here's an example from the ref doc:


                int total;
                // This method is called when an event occurs on the client
                // It takes a really long time to execute
                @Asynchronous
                public void calculateTotal() {
                total = someReallyComplicatedCalculation();
                }
                // This method is called as the result of the poll
                // It's very quick to execute
                public int getTotal() {
                return total;
                }



                So in this example, your main thread from another Seam component would invoke calculateTotal() and then invoke getTotal() presumably using some kind of polling mechanism (b/c how would you know when the calculateTotal() method has completed processing so you know you can now call getTotal()?)


                Is this what you're trying to accomplish???


                And you need to be very careful about what you store in the application context as it is available for all HttpSessions and thus will typically only be released on server restart (i.e. JVM shutdown).



                Application context
                is mainly useful for holding static information such as configuration data, reference data or
                metamodels. For example, Seam stores its own configuration and metamodel in the application
                context.
                • 5. Re: How to do @Asyncronous with Ajax and Conversations pattern?

                  My use case is essentially polling from a client (whether ajax:poll or seam remoting, it doesn't matter) to get a PARTIAL update from a previously started asynchronous process. The 'main' thread is the thread in context of the conversation, i.e. the single thread that loads the context upon each call from the client


                  So what I am I trying to accomplish?? I'm trying to get a partial update from an asynchronous thread into the conversation context so that the polling thread can access the partial processing results of the background process while it is still running.


                  You are right in that the example only gets you the final result and not a partial result as the 'total' int will remain at 0 until the entire background process has completed. This is not what I need... I need updates while the background thread is working!! and append those updates to a working set in the conversation.


                  Unfortunately the @Asynchronous examples are a bit lightweight and Dan only touches on the subject in his 'Seam in Action' book... so I'm really hoping someone out there has a good solid pattern / solution to this problem!!


                  Cheers


                  Troy

                  • 6. Re: How to do @Asyncronous with Ajax and Conversations pattern?
                    asookazian

                    What exactly will the following method do?


                    @Asynchronous
                    @Transactional
                    public void someProcessingWork(Object myDataRequiredFromConversation) {
                        // long running work done here, including db inserts / updates and
                        // creation of a data that ultimately needs to be put (partial data updates) 
                        // in the calling conversation context (if still active)
                    }



                    Specifically, what do you mean by creation of a data and what is a partial data update?  Sounds like there's a for loop or equivalent and the context variable is constantly updated until the loop is done processing.


                    I mean if your main thread is waiting for a context variable to be set in the someProcessingWork() method, then use a EJB3 timer or Quartz timer (if you know the duration or length of time it will typically take to process the asynchronous event/method) that will keep checking the context variable value during some pre-determined interval.


                    Sorry, I totally misunderstood the problem in the beginning of this thread...

                    • 7. Re: How to do @Asyncronous with Ajax and Conversations pattern?
                      asookazian

                      Ok, I think I may have a solution for you.  Refer to section 21.2 AJAX Progress Bar in the Yuan et al, 2nd. ed. Seam book.


                      As per SiA: By default, Seam uses the Java 5 concurrent library to execute the method in a background thread.


                      So I will post the code for the progressbar example in section 21.2 with my mods (I simply added an @Asynchronous method and another form/button in the xhtml page):


                      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
                      <html xmlns="http://www.w3.org/1999/xhtml"
                             xmlns:ui="http://java.sun.com/jsf/facelets"
                             xmlns:h="http://java.sun.com/jsf/html"
                                xmlns:f="http://java.sun.com/jsf/core">
                      
                      <head>
                           <title>Progress Bar</title>
                           
                           <style>
                      
                          div.slider-box
                          {
                            position: relative;
                            width: 200px;
                            height: 20px;
                            border: 1px solid #cccccc; 
                            background-color: white;
                            color: white;
                            margin-bottom: 4px;
                          }     
                      
                          div.slider-progress
                          {
                            color: black;
                            font-family: helvetica;
                            font-size: 9pt;
                            padding: 2px;
                            font-weight: bold;
                          }
                      
                           </style>
                      </head>
                      
                      <body>
                      
                        <script type="text/javascript" src="seam/resource/remoting/resource/remote.js">
                          <!--
                          // This space intentionally left blank
                          //-->
                        </script>
                      
                        <script type="text/javascript" src="seam/resource/remoting/interface.js?progressBarAction">
                          <!--
                          // This space intentionally left blank
                          //-->
                        </script>
                        
                        <script type="text/javascript" src="slider.js">
                          <!--
                          // This space intentionally left blank
                          //-->
                        </script>  
                        
                        <script type="text/javascript">
                          //<![CDATA[
                          
                      //    Seam.Remoting.setDebug(true);
                          
                          // don't display the loading indicator
                          Seam.Remoting.displayLoadingMessage = function() {};
                          Seam.Remoting.hideLoadingMessage = function() {};
                          
                          var progressBarAction = Seam.Component.getInstance("progressBarAction");
                          
                          function queryProgress() {
                            setTimeout("getProgress()", 250);
                          }
                          
                          function getProgress() {
                            progressBarAction.getProgress(progressCallback);    
                          }
                          
                          function progressCallback(progress) {  
                            progressBar.setPosition(progress.percentComplete);
                            if (progress.percentComplete < 100)
                              queryProgress();
                          }
                      
                          // ]]>
                        </script>  
                      
                        <h1>Seam Progress Bar Demo</h1>
                         
                        <p>Click on the button below to start the long running server action, which displays the progress on an AJAX progress bar.</p>
                      
                        <h:form onsubmit="queryProgress();return true;"> 
                         
                          <h:commandButton value="Go!" action="#{progressBarAction.doSomething}"/>    
                            
                        </h:form>
                        <br/><br/>
                        <h:form>       
                          <h:commandButton value="Go Asynch!" action="#{progressBarAction.doAsynchSomething}"/>  
                        </h:form>
                        
                        <div id="progressBar"></div>
                      
                        <script type="text/javascript">
                          //<![CDATA[
                          
                                var progressBar = qfSliderFactory.addSlider("progressBar");
                             progressBar.setMaxValue(100);
                             progressBar.setWidth(200);
                             progressBar.setClassName("slider-box");
                             progressBar.setUsedColour("#0000cf");
                             progressBar.setShowProgress(true);
                             progressBar.setProgressClassName("slider-progress");
                             progressBar.repaint();   
                      
                          // ]]>
                        </script>    
                      
                      </body>
                      </html>
                      



                      import java.util.Random;
                      
                      import javax.ejb.Stateless;
                      
                      import org.jboss.seam.annotations.In;
                      import org.jboss.seam.annotations.Logger;
                      import org.jboss.seam.annotations.Name;
                      import org.jboss.seam.annotations.async.Asynchronous;
                      import org.jboss.seam.log.Log;
                      
                      @Stateless
                      @Name("progressBarAction")
                      public class ProgressBarAction implements ProgressBar {
                      
                        @In(create = true)
                        Progress progress;
                        
                        @Logger Log log;
                      
                        public String doSomething() {
                           
                          Random r = new Random(System.currentTimeMillis());
                          try {
                            for (int i = 1; i <= 100;)
                            {
                              Thread.sleep(r.nextInt(200));
                              progress.setPercentComplete(i);
                              i++;
                            }
                          }
                          catch (InterruptedException ex) {
                          }
                      
                          return "complete";
                        }
                      
                        public Progress getProgress()
                        {
                          return progress;
                        }
                        
                        //AS adding asynch method to spawn a new thread...
                        @Asynchronous
                        public void doAsynchSomething(){
                             log.info("begin doAsynchSomething()");
                             //do some long calculations...
                             try{
                                  Thread.sleep(10000);
                             }
                             catch (InterruptedException ex) {
                             }
                             log.info("end doAsynchSomething()");
                        }
                      }
                      



                      import javax.ejb.Local;
                      
                      import org.jboss.seam.annotations.async.Asynchronous;
                      import org.jboss.seam.annotations.remoting.WebRemote;
                      
                      @Local
                      public interface ProgressBar {
                        public String doSomething();
                        @WebRemote public Progress getProgress();
                        @Asynchronous public void doAsynchSomething();
                      }
                      
                      



                      import org.jboss.seam.ScopeType;
                      import org.jboss.seam.annotations.Name;
                      import org.jboss.seam.annotations.Scope;
                      
                      @Name("progress")
                      @Scope(ScopeType.SESSION)
                      public class Progress {
                      
                        private int percentComplete;
                      
                        public int getPercentComplete()
                        {
                          return percentComplete;
                        }
                      
                        public void setPercentComplete(int percentComplete)
                        {
                          this.percentComplete = percentComplete;
                        }
                      
                      }
                      



                      Note the following from the ref doc:  The asynchronous method is processed in a completely new event context and does not have
                      access to the session or conversation context state of the caller. However, the business process
                      context is propagated.

                      • 8. Re: How to do @Asyncronous with Ajax and Conversations pattern?

                        Hi, i'm newbie using seam. can't any one show me a complete example abouth long running processing please..
                        i was read abouth this in this forum, but i still confuse it.


                        Thank,


                        Iwan Nugraha