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() ); } } }