Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

In this article I will detail and example of executing an @Asynchronous task in Seam and hooking Richfaces progressBar to display the current status of that task. I also updated the example to show how to achieve the same polling of the results via a:poll.

This example is not necessary optimized, just written as a baseline example to work off of. Let's start with a basic Entity to work with:

@Entity
public class OrderRecord implements Serializable {
	
	private Long id;
	private Integer version;
	private String name;
	
	@Id @GeneratedValue
	public Long getId() { return id; }
	public void setId(Long id) { this.id = id; }
	
	@Version
	public Integer getVersion() { return version; }
	private void setVersion(Integer version) { this.version = version; }   	
	
	@Length(max=20)
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }   	
	
	public String toString() { return "Id: " + id + " Name: " + name; }
}

Now that we have the entity, let's create the Seam JavaBean. This bean will generate a random number which will determine the number of OrderRecords to insert, then when inserting those OrderRecords, random data will be generated to populate it.

AsyncExample.java

@Name("asyncExample")
@Scope(ScopeType.CONVERSATION)
public class AsyncExample {
	
	@Logger
	private Log log;

	@In
	EntityManager entityManager;
	
	int randomNumber;
	int min = 0;
	int max;
	long currentValue = -1;
        private boolean pollEnabled = false;
	
	@Begin(join=true)
	public void begin() {
		randomNumber = new Random().nextInt(5000);
		log.debug("Found " + randomNumber + " of OrderRecords to insert.");
		max = randomNumber;
	}

	private boolean buttonRendered = true;
	private boolean enabled = false;
	private Long startTime;
	private Long endTime;
	private int totalTime;
	private int operationsPerSecond;
	

	@Asynchronous
	public String startProcess(AsyncExample asyncExample) throws InterruptedException {
		asyncExample.setEnabled(true);
		asyncExample.setButtonRendered(false);
		long startTime = new Date().getTime();
		asyncExample.setStartTime(startTime);
		asyncExample.insertOrderRecords();
		return null;
	}
	
	@Transactional
	public void insertOrderRecords() {
		entityManager.joinTransaction();
		OrderRecord orderRecord = null;
		for(int i = 1; i <=  randomNumber; i++) {
			orderRecord = new OrderRecord();
			orderRecord.setName("Person" + new Random().nextInt());
			log.debug("Inserting orderRecord " + orderRecord);
			entityManager.persist(orderRecord);
			currentValue = i;
			entityManager.flush();
		}
		endTime = new Date().getTime();
		totalTime = (int)(endTime - startTime) / 1000;
		operationsPerSecond = max / totalTime;
	}

	public Long getCurrentValue() {

		if (isEnabled()) {
			if(currentValue < max) {
				return currentValue;
			}
		}
		if (startTime == null) {
			return Long.valueOf(-1);
		} else {
			return Long.valueOf(max + 1);
		}
	}

	public boolean isEnabled() { return enabled; }
	public void setEnabled(boolean enabled) { this.enabled = enabled; }

	public Long getStartTime() { return startTime; }
	public void setStartTime(Long startTime) { this.startTime = startTime; }

	public boolean isButtonRendered() { return buttonRendered; }
	public void setButtonRendered(boolean buttonRendered) { this.buttonRendered = buttonRendered; }

	public Log getLog() { return log; }

	public EntityManager getEntityManager() { return entityManager; }

	public int getMin() { return min; }
	public void setMin(int min) { this.min = min; }

	public int getMax() { return max; }
	public void setMax(int max) { this.max = max; }

	public int getRandomNumber() { return randomNumber; }
	public void setRandomNumber(int randomNumber) { this.randomNumber = randomNumber; }

	public void setCurrentValue(long currentValue) { this.currentValue = currentValue; }
	
	public Long getEndTime() { return endTime; }
	public void setEndTime(Long endTime) { this.endTime = endTime; }
	
	public int getTotalTime() { return totalTime; }
	public void setTotalTime(int totalTime) { this.totalTime = totalTime; }

	public int getOperationsPerSecond() { return operationsPerSecond; }
	public void setOperationsPerSecond(int operationsPerSecond) { this.operationsPerSecond = operationsPerSecond; }
        public boolean isPollEnabled() { return pollEnabled; }
	public void setPollEnabled(boolean pollingEnabled) { this.pollEnabled = pollingEnabled; }
}

Now for the asyncExample.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:a="http://richfaces.org/a4j" template="layout/template.xhtml">

	<ui:define name="body">

		<h:messages globalOnly="true" styleClass="message" />

		<!--  The following rich:panel contains code to handle viewing the asynchronous results via a:poll -->
		<rich:panel>
			<f:facet name="header">Asynchronous Process and a4j:poll</f:facet>
			
			<h:form id="beginLRCForm">
				<a:commandButton action="#{asyncExample.begin()}"
						 value="Begin LRC" 
						 reRender="asyncExampleForm, beginLRCForm"
						 rendered="#{!conversation.longRunning}"
						 style="margin: 9px 0px 5px;"
						 ignoreDupResponses="true"
						 eventsQueue="async"
						 requestDelay="200" />
			</h:form>

			<a:region>
        		  <h:form id="pollForm">
            		    <a:poll id="poll" interval="1000" enabled="#{asyncExample.pollEnabled}" reRender="poll,grid"/>
        		  </h:form>
    		        </a:region>

			<h:form id="startAsyncForm">
		        <h:panelGrid columns="2" width="80%" id="grid">
		            <h:panelGrid columns="1">
		                <h:outputText value="Polling Inactive" rendered="#{not asyncExample.pollEnabled}"></h:outputText>
		                <h:outputText value="Polling Active" rendered="#{asyncExample.pollEnabled}"></h:outputText>
		                <a:commandButton style="width:120px" id="control"
		                                   value="#{asyncExample.pollEnabled?'Stop':'Start'} Polling"
		                                   reRender="poll, grid">
		                    <a:actionparam name="polling" value="#{!asyncExample.pollEnabled}"
		                                     assignTo="#{asyncExample.pollEnabled}"/>
		                </a:commandButton>
		                <a:commandButton action="#{asyncExample.startProcess(asyncExample)}"
							 value="Insert Order Records" 
							 reRender="progressPanel2"
							 rendered="#{asyncExample.buttonRendered and conversation.longRunning}"
							 style="margin: 9px 0px 5px;"
							 ignoreDupResponses="true"
							 eventsQueue="async"
							 requestDelay="200" />
		            </h:panelGrid>
		            <h:outputText id="serverDate" 
		            			  style="font-size:16px" 
		            			  value="#{asyncExample.currentValue} of #{asyncExample.max} processed"
		            			  rendered="#{asyncExample.pollEnabled}"/>
		        </h:panelGrid>
		    </h:form>
		</rich:panel>


		<!--  This rich:panel contains the code to view the asynchronous results via rich:progressBar -->
		<rich:panel>
			<f:facet name="header">Asynchronous Process and the rich:progressBar</f:facet>

			<h:form id="asyncExampleForm">

				<a:commandButton action="#{asyncExample.begin()}"
						 value="Begin LRC" 
						 reRender="asyncExampleForm"
						 rendered="#{!conversation.longRunning}"
						 style="margin: 9px 0px 5px;"
						 ignoreDupResponses="true"
						 eventsQueue="async"
						 requestDelay="200" />
				
		       <a:outputPanel id="progressPanel2">
					<rich:progressBar value="#{asyncExample.currentValue}"
							  interval="1000" 
							  label="#{asyncExample.currentValue} of #{asyncExample.max} processed"
							  enabled="#{asyncExample.enabled}" 
							  minValue="#{asyncExample.min}" 
							  maxValue="#{asyncExample.max}"
							  reRenderAfterComplete="progressPanel2"
							  ignoreDupResponses="true"
							  eventsQueue="async"
							  requestDelay="200" >
						<f:facet name="complete">
		                    <br />
		                    <h:outputText value="Process Complete. There were #{asyncExample.max} records inserted over #{asyncExample.totalTime} seconds for an average of #{asyncExample.operationsPerSecond} Records / second" />
		                </f:facet>
					</rich:progressBar>
				</a:outputPanel>
				
				<a:commandButton action="#{asyncExample.startProcess(asyncExample)}"
						 value="Insert Order Records" 
						 reRender="progressPanel2"
						 rendered="#{asyncExample.buttonRendered and conversation.longRunning}"
						 style="margin: 9px 0px 5px;"
						 ignoreDupResponses="true"
						 eventsQueue="async"
						 requestDelay="200" />

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


When accessing the page, you must first start the Long Running conversation by clicking the Begin LRC button. After the LRC is started you'll see the button Insert Order Records. Click that, wait for the records to be inserted, then metrics will show up on the operation. In my example I received:

Process Complete. There were 4862 records inserted over 43 seconds for an average of 113 Records / second
8 comments:
 
09. Mar 2009, 22:49 America/New_York | Link

Hi, I tried this example and got the following exception:

2009-03-09 18:09:59,640 ERROR
[org.jboss.ejb.txtimer.GeneralPurposeDatabasePersistencePlugin]
Cannot serialize: AsynchronousInvocation(asyncExample.startProcess()) 
java.io.NotSerializableException: java.lang.reflect.Method

java.lang.reflect.Method is indeed not serializable, is there some configuration not mentioned in the article needed to use this?

My components.xml contains the entry:

    <async:timer-service-dispatcher />
 
02. May 2009, 12:09 America/New_York | Link

Thanks for the useful article. I spent the day yesterday trying to get my a4j:progressBar working.

Unfortunately I'm still experiencing some problems...

I get this exception after the long running process completes:

Do not start long-running conversations in direct calls to EJBs

Also, when an exception occurs inside the asynchronous method call, it just gets swallowed. No error appears to the user, I even tried added an error using FacesMessages, but I just got a NullPointerException.

} catch (Throwable t) {
	log.error("Exception during indexing operation.", t);
	facesMessages.add(Severity.ERROR, "Exception during indexing operation. See log for details.");
}

Some more info...

I start the conversation from the menu link, probably not such a good idea?

<s:link view="/admin/indexer.xhtml" value="Indexer" propagation="begin"/>

Why did you take this approach:

When accessing the page, you must first start the Long Running conversation by clicking the Begin LRC button. After the LRC is started you'll see the button Insert Order Records. Click that, wait for the records to be inserted, then metrics will show up on the operation.

From my code below, you will notice that I'm using a Conversation scoped POJO. Not sure if I need to include the @Transactional annotation, since my app is running as an EAR on JBoss-4.2.3GA?

Here's my code:

@Name("indexer")
@Scope(ScopeType.CONVERSATION)
public class IndexerBean  {

	@Logger
	private Log log;

	static final int BATCH_SIZE = 1000;

	private boolean enabled = false;
	private boolean buttonRendered = true;
	private float index;
	private long total;
	private long percentage = -1;

	@In(create=true)
	EntityManager entityManager;

	@In(create=true)
    FacesMessages facesMessages;

	private Stopwatch stopwatch = new Stopwatch();

	@Asynchronous
	public String startProcess(IndexerBean indexerBean){
		indexerBean.setEnabled(true);
		indexerBean.setButtonRendered(false);
		indexerBean.index();
		return null;
	}

	@Begin(join=true)
	public void index() {
		stopwatch.start();

		Session session = (Session)entityManager.getDelegate();

		session.setFlushMode(FlushMode.MANUAL); //Disable flush operations
		session.setCacheMode(CacheMode.IGNORE); //Disable 2nd-level cache operations

		final FullTextSession ftSession =
			org.hibernate.search.Search.createFullTextSession( session );

		indexAllClasses(ftSession, ProductInfo.class);

		stopwatch.stop();
		setButtonRendered(true);
	}

	@SuppressWarnings("unchecked") @End
	private void indexAllClasses(FullTextSession fullTextSession, Class... entityTypes){
		try {
			//First calculate the total records to index
			for (Class entityType : entityTypes){
				Query query = entityManager.createQuery("select COUNT(e) from "+entityType.getName()+" e");
				total += (Long)query.getSingleResult();
			}
			//Perform indexing
			for (Class entityType : entityTypes){
				//read the data from the database
				//Scrollable results will avoid loading too many objects in memory
				ScrollableResults results = fullTextSession.createCriteria( entityType )
				.scroll( ScrollMode.FORWARD_ONLY );

				while( results.next() ) {
					index++;
					percentage = (long)((index / total) * 100);
					fullTextSession.index( results.get(0) );
					if (index % BATCH_SIZE == 0) {
						fullTextSession.clear();
					}
				}
			}
		} catch (Throwable t) {
			log.error("Exception during indexing operation.", t);
//this doesn't work
			facesMessages.add(Severity.ERROR, "Exception during indexing operation. See log for details.");
		}
	}

	public long getPercentage() {
		return percentage;
	}

	public long getRunningTime(){
		return stopwatch.getRunningTime();
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public boolean isButtonRendered() {
		return buttonRendered;
	}

	public void setButtonRendered(boolean buttonRendered) {
		this.buttonRendered = buttonRendered;
	}

}
 
12. Oct 2009, 12:23 America/New_York | Link

may be a simple but i think important question.

how can i get rid of the button for beginning LRC?

no user would understand why to click to buttons.... ;-)

 
12. Oct 2009, 13:20 America/New_York | Link
ok, solved myself. i added action="#{asyncExample.begin}" in the page tag of pages.xml
 
01. Dec 2009, 16:40 America/New_York | Link

Any chance you could explain why you have to use the @Transactional annotation on the insertOrderRecords() method. I know what this annotation does so need to explain the @Transactional annotation - just why you had to add it here. (is it because you are starting a new thread and want to pass the current tx to it or something?)

Thanks,

Philip

 
16. Feb 2010, 09:33 America/New_York | Link

Hi, i'm newbie using seam and i have a problem with long running processing. i'm still confuse how to work with @Asynchronous or thread. Please any one can help me to show me a complete axample for long runing processing or send me via my mail.

Thank you,

Iwan

 
19. Jul 2010, 03:06 America/New_York | Link

I had a lot of trouble getting the example to work. However I was able to eventually find a solution, and thought I should share it here:

The action method on a conversation scoped bean:

public String startProcess() { ExportProcessor exportProcessor = (ExportProcessor) Component.getInstance("exportProcessor"); setEnabled(true); QueryParser qp = getQueryParser(); List<String> vars = new ArrayList<String>(); List<Object> values = new ArrayList<Object>(); for (int i = 0; i < qp.getParameterValueBindings().size(); i++) { vars.add(qp.getParameterName(i)); ValueExpression ve = qp.getParameterValueBindings().get(i); values.add(ve.getValue()); } progress = new Progress(); exportProcessor.generateDBF(qp.getEjbql(), vars.toArray(new String[vars.size()]), values.toArray(), tableForExport, selectedFields, getReportDateValue(), getSelpaCodeValue(), currentUser.getId(), progress); return null; }

The problem here was that the query I needed to use was full of EL variables, which I needed to resolve before passing it to the asynchronous method. Diverging from the example, I moved the asynchronous method to a seperate bean (exportProcessor).

@Asynchronous public String generateDBF(String queryString, String[] vars, Object[] values, String tableForExport, List<String> selectedFields, Date rptDate, String sc, Integer currentUserId, Progress progress) { Session session = (Session) entityManager.getDelegate(); session.setFlushMode(FlushMode.MANUAL); session.setCacheMode(CacheMode.IGNORE); Query count = session.createQuery(QueryUtil.convertCountQuery(queryString)); Query results = session.createQuery(queryString); for (int i = 0; i < values.length; i++) { count.setParameter(vars[i], values[i]); results.setParameter(vars[i], values[i]); } progress.setMax(((Long) count.uniqueResult()).intValue()); ScrollableResults sr = results.scroll(ScrollMode.FORWARD_ONLY); ...... (heavy data processing here) }

The key to getting this to work was passing in the Progress object (placeholder for current record, max records, boolean isfinished) from the initial action to the asynchronous method. I also was stumped by some annoying integer division problems, so remember when you're calculating percentages, cast your ints to floats ;) Good luck

 
22. Oct 2010, 06:51 America/New_York | Link

Hi there, I just created similar post in my blog and then find this article. yes, the key think here is to pass Progress object with all parameters required for the async-method since it will be executed in different context (not in your conversation). http://achorniy.wordpress.com/2010/10/22/show-dynamic-process-progress-in-seam-richfaces/

I also looking forward to try to use a4j:push to achieve less server-loading and less-traffic usage.