Time consuming processes with Seam, Richfaces a4j:poll and Quartz

Today I had a hard time implementing time-consuming processes with AJAX status updates. I stripped down everything to a minimal working example. The technologies used in this example are

  • Seam 2.2.2
  • Richfaces 3.3
  • Quartz 1.6

So here is what I wanted to achieve: The user of my application should click a button and in the background, a long running process should do some database stuff. Since this could take a while, I wanted to show some progress to the user. I did not want a progress bar, but I wanted to display some text to the user, depending on what the long-running tasks current status was.

I stripped it all down to an minimal example, which starts a long running process (huge for loop which generates random UUIDs) when a button is clicked and displays the current UUID to the user. With this basic example I hope you can get the ideas behind the a4j:poll component and how to do asynchronous long-running tasks with Seam. I must admit, that I was heavily(!) inspired by Andrey Chorniys blog post Show dynamic progress of time-consuming process in Seam/RichFaces where I shamelessly stole code and adapted it to my needs.

Here are the neccessary steps to get started


Step 0: Setting up Quartz

I would recommend to read the GREAT blog post Quartz Scheduling with JBoss Seam by Nico Dewet, which covers the basic steps. Since this is not the topic of this post, here is just a small summary what to do:

  • Create a seam.quartz.properties file in the /resources folder of your Seam project
  • Add to you components.xml file
  • Modify your build.xml file to deploy the created seam.quartz.properties file when building your project
  • Add quartz.jar to your deployed-jars-ear.list file
  • I think it is not neccessary to use Quartz, since you can use the EJB3 Timer or the Seam Standard Timer. I did not try it, but I would lik to hear you experiences with other approaches!

    Step 1: Creating a JavaBean which holds all the progress state

    We must create a Serializable(!!!) class which is essentially a JavaBean with all our state for the progress. For brevity my Example ProgressBean just has a value property. Here is the code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    /**
     * @author dobermai
     */
    public class ProgressBean implements Serializable{
     
        private String value;
     
        public String getValue() {
            return value;
        }
     
        public void setValue(final String value) {
            this.value = value;
        }
    }

    Step 2: Create a Seam Bean which starts our long running progress

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    import org.jboss.seam.annotations.AutoCreate;
    import org.jboss.seam.annotations.Name;
    import org.jboss.seam.annotations.async.Asynchronous;
     
    /**
     * @author dobermai
     */
    @Name("longProcess")
    @AutoCreate
    public class LongProcess {
        private ProgressBean progress;
     
        @Asynchronous //Remember, this must be asynchronous, because it must run in the background
        public void startProcess(ProgressBean progress) {
     
            this.progress = progress;
            runProcess();
        }
     
        private void runProcess() {
     
        //Here we shold do our long
     
            for (int i = 0; i < Integer.MAX_VALUE - 3; i++) {
                //I just wanted to see very 10000 UUID
                if (i % 100000 == 0) {
     
                    String str = java.util.UUID.randomUUID().toString();
                     //Don't forget to update the state which should be displayed!
                    progress.setValue(str);
                }
            }
        }
    }

    So we update the state every 10000 generated UUIDs and these UUIDs should be displayed in our view. We can of course inject an entityManager for database tasks. If we need some parameters passed, we can use our progressBean for delivering us the parameters. This is of course not the cleanest way, but it is sufficient for me.

    Step 3: Create a Seam Bean which is Conversation Scoped

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    import org.jboss.seam.ScopeType;
    import org.jboss.seam.annotations.Begin;
    import org.jboss.seam.annotations.In;
    import org.jboss.seam.annotations.Name;
    import org.jboss.seam.annotations.Scope;
     
    /**
     * @author dobermai
     */
    @Name("conversationBean")
    @Scope(ScopeType.CONVERSATION)
    public class ConversationBean {
        private ProgressBean progressBean = new ProgressBean();
        @In
        LongProcess longProcess;
     
        @Begin(join = true) //We must start a new conversation here!
        public void startProcess() {
            /*Set all the relevant parameters for the long running task. The value parameter is shared between the task and this bean. If we want to pass parameters to the long running task, we can set them on the progressBean */ 
            progressBean.setValue("initial");
            longProcess.startProcess(progressBean); //Here we start our long running task in the background
        }
     
        public ProgressBean getProgressBean() {
            return progressBean;
        }
    }

    Remember to start a new long running conversation, otherwise we do not see the results in our view (of course).

    Step 4: Create a XHTML view

    <!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="layout/template.xhtml">

    <ui:define name="body">
    <a4j:region>
    <h:form>
    <a4j:poll id="poll" interval="500"
    reRender="commandPanel,progressPanel"/>
    </h:form>
    </a4j:region>

    <h:form>
    <h:panelGroup id="commandPanel">
    <a4j:commandButton action="#{conversationBean.startProcess()}"
    value="Start"
    reRender="commandPanel,progressPanel">
    </a4j:commandButton>
    <a4j:outputPanel id="progressPanel">
    <h:outputText
    value="#{conversationBean.progressBean.value}"/>
    </a4j:outputPanel>
    </h:panelGroup>

    </h:form>
    </ui:define>
    </ui:composition>

    When clicking the Start button, the polling will begin and we see the current value of the progress on our page.

    I hope this will get you started to develop AJAXy applications with Richfaces and Seam. The interval for the polling is probably too short for production code, since this can result in huge traffic. A better approach could be to use the a4j:push method, but for me the polling is sufficient.

    Share this Diese Icons verlinken auf Bookmark Dienste bei denen Nutzer neue Inhalte finden und mit anderen teilen können.
    • MisterWong
    • del.icio.us
    • Google Bookmarks
    • Facebook
    • TwitThis
    • DZone
    • Digg
    • Print

2 Gedanken zu “Time consuming processes with Seam, Richfaces a4j:poll and Quartz

  1. Pingback: What can I do to make a job search more efficient and less time consuming? | best job hunting guide

  2. It is necessary to inform that the bijection context is obviusly unavailable in assync methods, so if you want to get some stuff from the client to send to the assync method you must pass it the traditional method param way, and not via @In.

    Without this info, this and other tutorials can be misleading for some less experienced in seam.

    „The asynchronous method is processed in a fresh event context, and has no access to the session or conversation context state of the caller. However, the business process context is propagated.“

Schreib einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *