JSP Progress Bars
by Andrei Cioroianu06/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:
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
|
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 |


