package com.waldura.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; /** * This class generates a report. A servlet, it receives report parameters * and processes them to generate the appropriate report. The report is * saved to disk and sent to the client. *
* Some reports take a long time to create, and the client browser may time * out while waiting for the report. In order to prevent this, * report generation is handled asynchronously: if the report takes too * long to complete, the user is redirected to a wait page, as many times * as it takes to finish the report. *
* The asynchronous generation of reports is implemented by * spawning a thread, and redirecting the * user to page saying please wait, your report is being * generated. That page checks back with us, the * servlet, to get the report when it is complete. *
* But when should the client check for a completed report? Ideally, as often * as possible, so * that the user doesn't to wait any longer than it takes to generate the * report. Doing so would be akin to polling the server for the new * report, and, as we all know, polling doesn't scale too well. * Imagine many clients all constantly checking whether their report * is ready... *
* To solve this, we could include a delay in the "wait for report" * page, and have the page redirect to us (the servlet) every once in a * while. But how often? The bigger the delay, the more scalable we are * (clients check less often); but the longer the client may have to wait * for no reason -- since we cannot predict when the report is complete, * the client necessarily has * to wait a fixed amount of time. The report may be finished, the client * would be kept waiting. *
* The best approach is to wait on the server rather than on the client. * Clients should check with us often, but we (the servlet) block and wait * for the * report to be completed. This is almost similar to the original situation, * where clients contact the server and wait for the report to finish. * The major difference though, is that we can now control how long * clients wait, and prevent their browser from timing-out by redirecting * to the "wait page" every so often. *
* See {@link #generateReportAndRedirectClient( PUsessionController,
* HttpSession,
* HttpServletRequest,
* HttpServletResponse ) }
* for a more in-depth explanation.
*
* @author Renaud Waldura, 6/21/2001
*/
public class HandleGenReportRequest extends HttpServlet
{
/**
* This is a key into the session for the object handling the report generation.
*/
public static final String REPORT_GENERATION = "yourReportIsBeingGenerated";
/**
* This is how long we wait while a report is being generated before
* telling the client to wait (again). Value in seconds.
* This value must be smaller than the maximum amount of time any browser
* can be kept waiting for (the HTTP time-out). The current value is
* really just my guess at what a standard value is.
*/
public static final int REPORT_GENERATION_DELAY = 43;
/**
* This is the page the client is redirected to, after waiting for more
* than REPORT_GENERATION_DELAY seconds and the report
* still isn't finished.
*/
public static final String WAIT_FOR_REPORT_URL = "/devportal/waitForReport.jsp";
/**
* Process the HTTP request for a report.
*/
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// get the session
HttpSession session = req.getSession(true);
// get the session controller
PUsessionController pusc = (PUsessionController) session.getAttribute( PUsessionController.ID );
if (pusc == null)
{
// oops -- session timed-out?
ServletException e = new ServletException( "Your session has timed out. You must be logged on in order to generate a report." );
log( "User wasn't logged on when trying to generate a report. Their session had probably timed out: " + e );
throw e;
}
try
{
// generate report and send it to client
generateReportAndRedirectClient(pusc, session, req, resp);
}
catch (Exception e)
{
log( "Report generation failed: " + e );
throw new ServletException( "Report generation failed! ", e );
}
}
/**
* Generate a report, as specified by the request, and
* send it to the client. If the report generation takes too long,
* the client is redirected to a wait page.
*
* The high-level pseudo-code for this method is the following: *
* get thread from session * * if no thread * create one and start report generation * store it into the session * * wait for that thread to complete * * if timed-out while waiting * redirect the user to a "wait for your report" page * else * remove thread from session * redirect user to the generated report ** The "wait page" always redirects back to us (this servlet). In * effect, the user bounces back and forth between this servlet * and the wait page till the report has been generated. Most of the * time spent waiting for the report is spent inside of this method, * on the server side. * * @see ReportGeneration * * @throws Exception any error that may have occurred in the * asynchronous generation of the report. */ public void generateReportAndRedirectClient( PUsessionController pusc, HttpSession session, HttpServletRequest request, HttpServletResponse response ) throws Exception { // get the thread generating the report ReportGeneration reportGeneration = (ReportGeneration) session.getAttribute(REPORT_GENERATION); if (reportGeneration == null) { // no thread yet; first create a report engine GenReport reportEngine = new GenReport(); // and create a new thread to run it reportGeneration = new ReportGeneration(reportEngine); // stuff it into the session session.setAttribute(REPORT_GENERATION, reportGeneration); // initialize both objects from the request initializeReportingEngine(reportEngine, reportGeneration, pusc, request); log("initialized reporting engine"); // and kick off the thread reportGeneration.start(); log("started async report generation"); } log("waiting for report generation to complete..."); reportGeneration.waitForCompletion(REPORT_GENERATION_DELAY); if (!reportGeneration.isFinished()) { // report generation still hasn't completed log("report not ready; redirecting back to wait page"); response.sendRedirect(WAIT_FOR_REPORT_URL); } else // report generation is complete { // remove thread from the session session.removeAttribute(REPORT_GENERATION); // check for errors if (reportGeneration.getException() != null) throw reportGeneration.getException(); log("report is now complete; redirecting to generated report"); response.sendRedirect( reportGeneration.getReportURL() ); } } }