ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

JSP Progress Bars

by Andrei Cioroianu
06/11/2003

Many web and enterprise applications must perform CPU-intensive operations, such as complex database queries or heavy XML processing. These tasks are handled by database systems or middleware components, but the results are presented to the user with the help of JSP. This article shows how to implement the front tier in order to improve the user experience and reduce the server load.

When a JSP invokes an expensive operation whose result cannot be cached (on the server side), the user will have to wait each time he requests the page. Eventually, the user will lose his patience and he'll click the browser's Refresh or Reload button. This leads to ignored page requests that do heavy processing.

This article's example solves the problem described above by starting the heavy task in a separate thread. The JSP page returns an immediate response indicating that the task was started and is running. From time to time, the page is reloaded automatically in order to report the progress of the heavy task that continues to run in its own thread until it is completed. The user may stop the task only if the application allows such an action. In the end, the JSP page reports the result.

Simulating a Heavy Task

This section presents the TaskBean class, which implements java.lang.Runnable so that its run() method can be executed in a thread that is started from a JSP page (named start.jsp). Another JSP page (named stop.jsp) will let the user stop the run() method's execution. The TaskBean class also implements the java.io.Serializable interface so that it can be used as a JavaBean in the JSP pages:

package com.devsphere.articles.progressbar;

import java.io.Serializable;

public class TaskBean implements Runnable, Serializable {
    private int counter;
    private int sum;
    private boolean started;
    private boolean running;
    private int sleep;

    public TaskBean() {
        counter = 0;
        sum     = 0;
        started = false;
        running = false;
        sleep   = 100;
    }
}

The "heavy task" implemented by TaskBean computes 1 + 2 + ... + 100. Instead of using the well-known formula for computing this sum -- 100 * (100 + 1) / 2 = 5050 -- the run() method calls work() 100 times. See its code below. The Thread.sleep() call makes sure that the task will take about 10 seconds.

protected void work() {
    try {
        Thread.sleep(sleep);
        counter++;
        sum += counter;
    } catch (InterruptedException e) {
        setRunning(false);
    }
}

The status.jsp page calls the following methods to find out the task status. The getPercent() method tells how much of the task was executed:

public synchronized int getPercent() {
    return counter;
}

The isStarted() method returns true if the task was started:

public synchronized boolean isStarted() {
    return started;
}

The isCompleted() method returns true if the task was completed:

public synchronized boolean isCompleted() {
    return counter == 100;
}

The isRunning() method returns true if the task is still running:

public synchronized boolean isRunning() {
    return running;
}

The setRunning() method is called by start.jsp and stop.jsp. When the running parameter is true, the task is also marked as started. Calling setRunning(false) will tell the run() method to stop its execution.

public synchronized void setRunning(boolean running) {
    this.running = running;
    if (running)
        started  = true;
}

The getResult() method returns the computed sum if the task was completed. Otherwise, it returns null:

public synchronized Object getResult() {
    if (isCompleted())
        return new Integer(sum);
    else
        return null;
}

The run() method calls work() as long as the running flag is true and completed is false. A real application's run() method would execute a complex SQL query, parse a large XML document, or invoke a CPU-intensive EJB method. Note that the heavy task might be executed on a remote server. The JSP page reporting the result has two choices: to wait for the task completion or to use a progress bar.

public void run() {
    try {
        setRunning(true);
        while (isRunning() && !isCompleted())
            work();
    } finally {
        setRunning(false);
    }
}

Starting the Task

To run the example, download the article's source code archive, unzip it, deploy the progressbar.war web application and open http://localhost:8080/progressbar/ in your browser. This will invoke start.jsp, which is the application's welcome page declared in the web.xml descriptor:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <welcome-file-list>
        <welcome-file>start.jsp</welcome-file>
    </welcome-file-list>
</web-app>

The start.jsp page starts a thread to run the "heavy task," and then it forwards the HTTP request to status.jsp, whose response looks like Figure 1:

Running Task
Figure 1. A running task

The start.jsp page creates a TaskBean instance using the <jsp:useBean> tag. The session scope allows other pages to retrieve the same bean object when the HTTP requests are coming from the same browser.

The page calls session.removeAttribute("task") to make sure that <jsp:useBean> creates a new bean object rather than retrieving an old object, which might have been created by an earlier JSP request during the same user session.

Here is the code of the start.jsp page:

<% session.removeAttribute("task"); %>

<jsp:useBean id="task" scope="session"
    class="com.devsphere.articles.progressbar.TaskBean"/>

<% task.setRunning(true); %>

<% new Thread(task).start(); %>

<jsp:forward page="status.jsp"/>

Related Reading

JavaServer Pages
By Hans Bergsten

After creating the TaskBean object, start.jsp creates a Thread, passing the bean object as a Runnable instance. The start() method causes the newly created thread to execute the run() method of the TaskBean object.

There are now two threads running concurrently: the thread that executes the JSP page (the "JSP thread") and the thread created by the JSP page (the "task thread"). The start.jsp page uses <jsp:forward> to invoke status.jsp, which shows the progress bar and the task status. Both JSP pages are executed in the same JSP thread.

Setting the running flag to true in start.jsp makes sure that the "running" status is reported to the user even if the task thread's run() method doesn't get started by the time when the status.jsp page is executed in the JSP thread.

The two lines of Java code that set the running flag to true and start the task thread could be moved into the TaskBean class to form a new method that would be called from the JSP page. It usually is a good idea to move as much code as possible into the Java classes. In the case of this example, placing new Thread(task).start() into start.jsp makes very clear that the JSP thread creates and starts the task thread.

You must be careful when dealing with threads in JSP pages. Keep in mind that your threads and the JSP threads are executed concurrently. This is similar to desktop programming, when you have a thread processing GUI events and other threads executing application-specific tasks. However, in the JSP environment, the same page may be executed in multiple threads on behalf of multiple users. There are also situations when the same user makes multiple requests to the same page and those HTTP requests may be served concurrently, even though they are coming from the same user.

Pages: 1, 2

Next Pagearrow