com.slamd.admin.AdminServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.slamd.admin.AdminServlet.java

Source

/*
 *                             Sun Public License
 *
 * The contents of this file are subject to the Sun Public License Version
 * 1.0 (the "License").  You may not use this file except in compliance with
 * the License.  A copy of the License is available at http://www.sun.com/
 *
 * The Original Code is the SLAMD Distributed Load Generation Engine.
 * The Initial Developer of the Original Code is Neil A. Wilson.
 * Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
 * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
 * All Rights Reserved.
 *
 * Contributor(s):  Neil A. Wilson
 */
package com.slamd.admin;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import netscape.ldap.LDAPException;

import org.apache.commons.fileupload.DefaultFileItemFactory;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;

import com.sleepycat.je.DatabaseException;

import com.slamd.common.Constants;
import com.slamd.common.DynamicConstants;
import com.slamd.common.SLAMDException;
import com.slamd.db.DSMigrator;
import com.slamd.db.JobFolder;
import com.slamd.db.SLAMDDB;
import com.slamd.job.Job;
import com.slamd.job.JobClass;
import com.slamd.job.OptimizingJob;
import com.slamd.job.UnknownJobClass;
import com.slamd.jobgroup.JobGroup;
import com.slamd.parameter.BooleanParameter;
import com.slamd.parameter.IntegerParameter;
import com.slamd.parameter.InvalidValueException;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.parameter.PasswordParameter;
import com.slamd.parameter.PlaceholderParameter;
import com.slamd.parameter.StringParameter;
import com.slamd.report.ReportGenerator;
import com.slamd.server.SLAMDServer;
import com.slamd.server.SLAMDServerException;
import com.slamd.server.Scheduler;
import com.slamd.server.UploadedFile;
import com.slamd.stat.StatTracker;

import com.unboundid.util.Base64;

import static com.slamd.admin.AdminAccess.*;
import static com.slamd.admin.AdminConfig.*;
import static com.slamd.admin.AdminDebug.*;
import static com.slamd.admin.AdminJob.*;
import static com.slamd.admin.AdminJobGroup.*;
import static com.slamd.admin.AdminUI.*;

/**
 * This class serves as the single point of entry for the Web-based SLAMD
 * administration interface.  It provides a user-friendly mechanism for the end
 * user to access the SLAMD server.
 *
 *
 * @author   Neil A. Wilson
 */
public class AdminServlet extends HttpServlet {
    /**
     * The serial version UID for this serializable class.
     */
    private static final long serialVersionUID = 5218965023006249116L;

    /**
     * The name that will be used to identify the admin interface as a
     * configuration subscriber.
     */
    public static final String CONFIG_SUBSCRIBER_NAME = "User Interface";

    /**
     * The end of line character that will be used for the generated HTML content.
     */
    public static final String EOL = "\n";

    /**
     * The reference to this admin servlet.
     */
    public static AdminServlet adminServlet = null;

    /**
     * The reference to the SLAMD server with which this admin servlet is
     * associated, if there is one.
     */
    public static SLAMDServer slamdServer = null;

    /**
     * The access control manager for the administrative interface.
     */
    static AccessManager accessManager;

    /**
     * Indicates whether to always show advanced scheduling options when
     * scheduling a job.
     */
    static boolean alwaysShowAdvancedOptions;

    /**
     * Indicates whether the configuration database has been created and exists on
     * the disk.
     */
    static boolean configDBExists;

    /**
     * Indicates whether the graphing capability of SLAMD should be disabled.
     */
    static boolean disableGraphs;

    /**
     * Indicates whether the file upload capability of SLAMD should be disabled.
     */
    static boolean disableUploads;

    /**
     * Indicates whether to enable management of options that are only applicable
     * if the server will be run in restricted read-only mode.
     */
    static boolean enableReadOnlyManagement;

    /**
     * Indicates whether graphs should be generated in a new window.
     */
    static boolean graphInNewWindow;

    /**
     * Indicates whether the individual iterations of an optimizing job should be
     * hidden when viewing a list of completed jobs.
     */
    static boolean hideOptimizingIterations;

    /**
     * Indicates whether the SLAMD server should attempt to hide sensitive
     * information when displaying a job in read-only mode.  This sensitive
     * information can include the addresses of client systems, e-mail addresses
     * for job notification, and the values of any parameters that are marked
     * sensitive in the associated job.
     */
    static boolean hideSensitiveInformation;

    /**
     * Indicates whether to include the address of the SLAMD server in the title
     * of the generated HTML pages.
     */
    static boolean includeAddressInPageTitle;

    /**
     * Indicates whether the admin interface should automatically place the
     * current time in the start time text field when scheduling a new job.
     */
    static boolean populateStartTime;

    /**
     * Indicates whether the admin interface should operate in read-only mode.  In
     * read-only mode, users will only be able to view job information -- they
     * will not be allowed to schedule jobs or alter the configuration.
     */
    static boolean readOnlyMode;

    /**
     * Indicates whether the admin interface should restrict the set of jobs and
     * folders that are displayed when the server is operating in read-only mode.
     */
    static boolean restrictedReadOnlyMode;

    /**
     * Indicates whether the admin interface should allow users to search for
     * jobs when operating in read-only mode.
     */
    static boolean searchReadOnly;

    /**
     * Indicates whether the login ID of the currently-authenticated user will be
     * displayed in the navigation bar.
     */
    static boolean showLoginID;

    /**
     * Indicates whether the SLAMD Server Status link should be shown first or
     * last in the navigation sidebar.  By default it will be shown last.
     */
    static boolean showStatusFirstInSidebar;

    /**
     * Indicates whether the current time should be displayed in the sidebar.
     */
    static boolean showTimeInSidebar;

    /**
     * Indicates whether the SLAMD server is currently running.
     */
    static boolean slamdRunning;

    /**
     * Indicates whether access control should be used when determining whether to
     * allow a given operation.
     */
    static boolean useAccessControl;

    /**
     * Indicates whether the SLAMD server should blindly trust any SSL certificate
     * presented by the user directory.
     */
    static boolean userDirBlindTrust;

    /**
     * Indicates whether the connection to the user directory should be encrypted
     * using SSL.
     */
    static boolean userDirUseSSL;

    /**
     * The byte array containing the encoded SLAMD logo.
     */
    static byte[] slamdLogoBytes;

    /**
     * The configuration database for the SLAMD server.
     */
    static SLAMDDB configDB;

    /**
     * The decimal formatter used by the admin servlet.
     */
    static DecimalFormat decimalFormat;

    /**
     * The default width to use for generated graphs.
     */
    static int defaultGraphWidth;

    /**
     * The default height to use for generated graphs.
     */
    static int defaultGraphHeight;

    /**
     * The default height to use for resource monitor graphs.
     */
    static int defaultMonitorGraphHeight;

    /**
     * The port number to use to connect to the user directory.
     */
    static int userDirPort;

    /**
     * The maximum file size that will be allowed via upload.  A value of -1
     * indicates no limit.
     */
    static int maxUploadSize;

    /**
     * The request ID that will be used for the next request.
     */
    static int nextID;

    /**
     * The set of properties associated with the servlet configuration file.
     */
    static Properties configProperties;

    /**
     * The set of report generators that have been configured in the SLAMD server.
     */
    static ReportGenerator[] reportGenerators;

    /**
     * The SLAMD server scheduler.
     */
    static Scheduler scheduler;

    /**
     * The date formatter that will be used to format dates in a form that will be
     * stored in the directory server and also when users enter date information
     * via a form.
     */
    static SimpleDateFormat dateFormat;

    /**
     * The date formatter that will be used to format dates that will be displayed
     * to the end user.
     */
    static SimpleDateFormat displayDateFormat;

    /**
     * The lines that should be added to the HTML header for generated pages.
     */
    static String addedHeaderLines = null;

    /**
     * The HTML target that will be used to determine whether to create a new
     * window for the specified content.
     */
    static String blankTarget;

    /**
     * The location on the filesystem under which the job classes may be found.
     */
    static String classPath;

    /**
     * The location of the configuration database files.
     */
    static String configDBDirectory;

    /**
     * The servlet configuration file that may be an alternate source for init
     * parameters.
     */
    static String configFile;

    /**
     * The HTML that should be displayed on the main page displayed when a user
     * initially accesses the SLAMD administrative interface.
     */
    static String defaultHTML = null;

    /**
     * The HTML that should be displayed at the bottom of every page generated.
     */
    static String pageFooter = null;

    /**
     * The HTML that should be displayed at the top of every page generated.
     */
    static String pageHeader = null;

    /**
     * The DN of the group/role that specifies who can add job classes.
     */
    static String resourceDNAddJobClass;

    /**
     * The DN of the group/role that specifies which users can be used by clients
     * to authenticate.
     */
    static String resourceDNAuthenticateClient;

    /**
     * The DN of the group/role that specifies who can cancel jobs.
     */
    static String resourceDNCancelJob;

    /**
     * The DN of the group/role that specifies who can delete jobs.
     */
    static String resourceDNDeleteJob;

    /**
     * The DN of the group/role that specifies who can delete job classes.
     */
    static String resourceDNDeleteJobClass;

    /**
     * The DN of the group/role that specifies who can disconnect clients.
     */
    static String resourceDNDisconnectClient;

    /**
     * The DN of the group/role that specifies who can edit the servlet config.
     */
    static String resourceDNEditServletConfig;

    /**
     * The DN of the group/role that specifies who can edit the SLAMD config.
     */
    static String resourceDNEditSLAMDConfig;

    /**
     * The DN of the group/role that specifies who can export job information.
     */
    static String resourceDNExportJob;

    /**
     * The DN of the group/role that specifies who has full access to anything.
     */
    static String resourceDNFullAccess;

    /**
     * The DN of the group/role that specifies who can manage real and virtual
     * job folders.
     */
    static String resourceDNManageJobFolders;

    /**
     * The DN of the group/role that specifies who can start and stop the SLAMD
     * server.
     */
    static String resourceDNRestartSLAMD;

    /**
     * The DN of the group/role that specifies who can start and stop the access
     * control manager.
     */
    static String resourceDNRestartACL;

    /**
     * The DN of the group/role that specifies who can view job information.
     */
    static String resourceDNViewJob;

    /**
     * The DN of the group/role that specifies who can view job class definitions.
     */
    static String resourceDNViewJobClass;

    /**
     * The DN of the group/role that specifies who can schedule jobs.
     */
    static String resourceDNScheduleJob;

    /**
     * The DN of the group/role that specifies who can view the servlet config.
     */
    static String resourceDNViewServletConfig;

    /**
     * The DN of the group/role that specifies who can view the SLAMD config.
     */
    static String resourceDNViewSLAMDConfig;

    /**
     * The DN of the group/role that specifies who can view SLAMD status info.
     */
    static String resourceDNViewStatus;

    /**
     * The style sheet that should be used for generated HTML pages.
     */
    static String styleSheet = null;

    /**
     * The location of the JSSE key store to use for establishing SSL-based
     * connections.
     */
    static String sslKeyStore;

    /**
     * The password for the JSSE key store.
     */
    static String sslKeyStorePassword;

    /**
     * The location of the JSSE trust store to use for establishing SSL-based
     * connections.
     */
    static String sslTrustStore;

    /**
     * The password for the JSSE trust store.
     */
    static String sslTrustStorePassword;

    /**
     * The reason that the administrative interface is unavailable.
     */
    static String unavailableReason;

    /**
     * The base DN under which the user accounts are located in the user
     * directory.
     */
    static String userDirBase;

    /**
     * The DN to use to bind to the user directory server.
     */
    static String userDirBindDN;

    /**
     * The password for the user directory bind DN.
     */
    static String userDirBindPW;

    /**
     * The host name or IP address for the user directory server.
     */
    static String userDirHost;

    /**
     * The name of the LDAP attribute that will be used to find user entries in
     * the user directory from the login ID that has been provided.
     */
    static String userIDAttribute;

    /**
     * The absolute path to the WEB-INF directory for the admin interface.
     */
    static String webInfBasePath;

    /**
     * Perform the one-time initialization for the servlet that is done when the
     * servlet is first loaded.
     */
    @Override()
    public void init() {
        adminServlet = this;

        // Initialize the static variables that won't get caught elsewhere
        slamdRunning = false;
        nextID = 0;
        dateFormat = new SimpleDateFormat(Constants.ATTRIBUTE_DATE_FORMAT);
        displayDateFormat = new SimpleDateFormat(Constants.DISPLAY_DATE_FORMAT);
        unavailableReason = "The servlet initialization did not complete " + "properly.";
        defaultGraphWidth = Constants.DEFAULT_GRAPH_WIDTH;
        defaultGraphHeight = Constants.DEFAULT_GRAPH_HEIGHT;
        defaultMonitorGraphHeight = Constants.DEFAULT_MONITOR_GRAPH_HEIGHT;
        decimalFormat = new DecimalFormat("0.000");
        webInfBasePath = getServletContext().getRealPath(Constants.DEFAULT_WEB_APP_PATH);
        showStatusFirstInSidebar = false;

        // Read the SLAMD logo into the corresponding byte array.
        slamdLogoBytes = new byte[0];
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("slamd_logo.gif");
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8192);

            int bytesRead;
            byte[] buffer = new byte[8192];
            while ((bytesRead = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, bytesRead);
            }

            slamdLogoBytes = outputStream.toByteArray();
        } catch (Exception e) {
        }

        // First, read the servlet configuration
        try {
            if (!readServletConfig(getServletConfig())) {
                // We don't need to set the unavailable reason here because
                // readServletConfig() will do that for us.
                return;
            }
        } catch (IOException ioe) {
            if ((configFile != null) && (configFile.length() > 0)) {
                unavailableReason = "Unable to read the servlet configuration file " + configFile + ":  " + ioe;
            } else {
                unavailableReason = "Unable to obtain the servlet configuration.";
            }

            return;
        }

        // Configure the access control manager if access control checking will be
        // used
        if ((!readOnlyMode) && useAccessControl) {
            accessManager = new AccessManager(userDirHost, userDirPort, userDirBindDN, userDirBindPW, userDirBase,
                    userIDAttribute, userDirUseSSL, userDirBlindTrust, sslKeyStore, sslKeyStorePassword,
                    sslTrustStore, sslTrustStorePassword);

            // Register all of the protected resources
            RequestInfo requestInfo = new RequestInfo(null, null);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_FULL, resourceDNFullAccess, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_RESTART_SLAMD, resourceDNRestartSLAMD,
                    false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_RESTART_ACL, resourceDNRestartACL, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_VIEW_SERVLET_CONFIG,
                    resourceDNViewServletConfig, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_EDIT_SERVLET_CONFIG,
                    resourceDNEditServletConfig, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_VIEW_SLAMD_CONFIG,
                    resourceDNViewSLAMDConfig, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_EDIT_SLAMD_CONFIG,
                    resourceDNEditSLAMDConfig, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_VIEW_STATUS, resourceDNViewStatus, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_DISCONNECT_CLIENT,
                    resourceDNDisconnectClient, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_VIEW_JOB, resourceDNViewJob, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_EXPORT_JOB, resourceDNExportJob, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_SCHEDULE_JOB, resourceDNScheduleJob,
                    false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_CANCEL_JOB, resourceDNCancelJob, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_DELETE_JOB, resourceDNDeleteJob, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_MANAGE_JOB_FOLDERS,
                    resourceDNManageJobFolders, false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_VIEW_JOB_CLASS, resourceDNViewJobClass,
                    false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_ADD_JOB_CLASS, resourceDNAddJobClass,
                    false);
            registerACL(requestInfo, Constants.SERVLET_INIT_PARAM_ACCESS_DELETE_JOB_CLASS, resourceDNDeleteJobClass,
                    false);

            // Start the access control manager.
            try {
                accessManager.startAccessManager();
            } catch (LDAPException le) {
                unavailableReason = "The access control manager was not able to be "
                        + "started because the connection to the " + "configuration directory could not be "
                        + "established:  " + le.getMessage();
                return;
            }
        }

        // Determine whether the configuration database has been created yet.  If
        // not, then set a flag so that the admin interface will prompt the user to
        // create it.
        configDBExists = false;
        try {
            configDBExists = SLAMDDB.dbExists(configDBDirectory);
            if (!configDBExists) {
                slamdServer = null;
                scheduler = null;
                configDB = null;
                unavailableReason = "The configuration database has not yet been " + "created.";
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            slamdServer = null;
            scheduler = null;
            configDB = null;
            unavailableReason = "Could not determine if the configuration " + "database already exists:  " + e;
            return;
        }

        // Create the SLAMD server instance.
        try {
            slamdServer = new SLAMDServer(this, readOnlyMode, configDBDirectory, sslKeyStore, sslKeyStorePassword,
                    sslTrustStore, sslTrustStorePassword);

            scheduler = slamdServer.getScheduler();
            configDB = slamdServer.getConfigDB();
        } catch (Exception e) {
            e.printStackTrace();
            slamdServer = null;
            scheduler = null;
            configDB = null;
            unavailableReason = "Could not create the SLAMD server instance:  " + e.getMessage();
            return;
        }

        // Register as a configuration subscriber
        configDB.registerAsSubscriber(ADMIN_CONFIG);
        ADMIN_CONFIG.refreshSubscriberConfiguration();

        // If we have made it to this point, then the server has started up and
        // should be ready to handle requests and for administrative interaction.
        slamdRunning = true;
    }

    /**
     * Indicates that the servlet engine is shutting down and that the appropriate
     * action should be taken to ensure that SLAMD is properly stopped.
     */
    @Override()
    public void destroy() {
        if (slamdServer != null) {
            slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "SLAMD servlet engine is shutting down.");

            if (slamdRunning) {
                slamdServer.stopSLAMD();
                slamdRunning = false;
            }
        }
    }

    /**
     * Receives an HTTP GET request from a client, interprets the request, and
     * generates the appropriate response.
     *
     * @param  request   Information about the HTTP GET request issued by the
     *                   client.
     * @param  response  Information about the HTTP response that will be
     *                   returned to the client.
     *
     * @throws  IOException  If a problem is encountered while reading from or
     *                       writing to the client.
     */
    @Override()
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // We don't need to distinguish between requests made via GET and those
        // made via POST, so just treat a GET the same as a POST.
        doPost(request, response);
    }

    /**
     * Receives an HTTP POST request from a client, interprets the request, and
     * generates the appropriate response.
     *
     * @param  request   Information about the HTTP POST request issued by the
     *                   client.
     * @param  response  Information about the HTTP response that will be
     *                   returned to the client.
     *
     * @throws  IOException  If a problem is encountered while reading from or
     *                       writing to the client.
     */
    @Override()
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // First, create the request info variable.
        RequestInfo requestInfo = new RequestInfo(request, response);
        requestInfo.requestID = getNextID();

        String section = requestInfo.section;
        String subsection = requestInfo.subsection;

        String dbgStr = request.getParameter(Constants.SERVLET_PARAM_HTML_DEBUG);
        requestInfo.debugHTML = ((dbgStr != null) && (dbgStr.length() > 0));

        requestInfo.generateSidebar = true;
        String param = request.getParameter(Constants.SERVLET_PARAM_HIDE_SIDEBAR);
        if ((param != null) && (param.equalsIgnoreCase(Constants.CONFIG_VALUE_TRUE))) {
            requestInfo.generateSidebar = false;
        }

        if (useAccessControl
                && ((requestInfo.userIdentifier == null) || (requestInfo.userIdentifier.length() == 0))) {
            handleUnauthenticatedUser(requestInfo);
        } else {
            // Initialize the variables used for access control.
            setAccessControlVariables(requestInfo);

            // Check to see if the SLAMD server is running.  If not, then display the
            // appropriate page to the user.
            if ((!configDBExists) && (!section.equals(Constants.SERVLET_SECTION_DOCUMENTATION))
                    && (!section.equals(Constants.SERVLET_SECTION_LICENSE_HTML))
                    && (!section.equals(Constants.SERVLET_SECTION_LICENSE_TEXT))
                    && (!section.equals(Constants.SERVLET_SECTION_SLAMD_LOGO))) {
                handleNoDB(requestInfo);
            } else if ((!slamdRunning) && (!section.equals(Constants.SERVLET_SECTION_CONFIG))
                    && (!section.equals(Constants.SERVLET_SECTION_STATUS))
                    && (!section.equals(Constants.SERVLET_SECTION_DOCUMENTATION))
                    && (!section.equals(Constants.SERVLET_SECTION_LICENSE_HTML))
                    && (!section.equals(Constants.SERVLET_SECTION_LICENSE_TEXT))
                    && (!section.equals(Constants.SERVLET_SECTION_SLAMD_LOGO))) {
                handleSLAMDUnavailable(requestInfo);
            } else {
                // See if the user just wants to view a job.  If so, then take them to
                // it.  This can make for shorter URLs if you are only interested in
                // viewing job information.
                String getJobID = request.getParameter(Constants.SERVLET_PARAM_GET_JOB);
                if ((getJobID != null) && (getJobID.length() > 0)) {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, null, getJobID);
                }

                // Determine what to do from the parameters that have been provided
                else if (section.equals(Constants.SERVLET_SECTION_SLAMD_LOGO)) {
                    handleSLAMDLogo(requestInfo);
                } else if (section.equals(Constants.SERVLET_SECTION_CONFIG)) {
                    if (subsection.equals(Constants.SERVLET_SECTION_CONFIG_SERVLET)) {
                        handleServletConfig(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_CONFIG_ACCESS)) {
                        handleAccessControlConfig(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_CONFIG_SLAMD)) {
                        handleSLAMDConfig(requestInfo);
                    } else {
                        handleConfig(requestInfo);
                    }
                } else if (section.equals(Constants.SERVLET_SECTION_DOCUMENTATION)) {
                    handleDocumentation(requestInfo);
                } else if (section.equals(Constants.SERVLET_SECTION_LICENSE_HTML)) {
                    handleHTMLLicense(requestInfo);
                } else if (section.equals(Constants.SERVLET_SECTION_LICENSE_TEXT)) {
                    handleTextLicense(requestInfo);
                } else if (section.equals(Constants.SERVLET_SECTION_STATUS)) {
                    if (subsection.equals(Constants.SERVLET_SECTION_STATUS_VIEW_LOG)) {
                        handleViewLog(requestInfo);
                    } else {
                        handleStatus(requestInfo);
                    }
                } else if (section.equals(Constants.SERVLET_SECTION_JOB)) {
                    if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_PENDING)
                            || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_RUNNING)
                            || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED)
                            || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GENERIC)) {
                        handleViewJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_AS_TEXT)) {
                        handleViewJobAsText(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_OPTIMIZING_AS_TEXT)) {
                        handleViewOptimizingJobAsText(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_LOG_MESSAGES)) {
                        handleViewJobLogMessages(requestInfo);
                        requestInfo.generateSidebar = false;
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GROUPS)) {
                        handleViewJobGroups(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GROUP)) {
                        handleViewJobGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_GROUP_DESCRIPTION)) {
                        handleEditJobGroupDescription(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_GROUP_PARAMS)) {
                        handleEditJobGroupParams(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_GROUP_JOB)) {
                        handleEditJobGroupJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_GROUP_OPTIMIZING_JOB)) {
                        handleEditJobGroupOptimizingJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ADD_GROUP)) {
                        handleAddJobGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ADD_JOB_TO_GROUP)) {
                        handleAddJobToGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ADD_OPTIMIZING_JOB_TO_GROUP)) {
                        handleAddOptimizingJobToGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_REMOVE_JOB_FROM_GROUP)) {
                        handleRemoveJobFromGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_REMOVE_GROUP)) {
                        handleRemoveJobGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SCHEDULE_GROUP)) {
                        handleScheduleJobGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CLONE_GROUP)) {
                        handleCloneJobGroup(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_REAL)) {
                        handleViewRealFolderList(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_LIST_REAL_FOLDERS)) {
                        handleListRealFolders(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_LIST_VIRTUAL_FOLDERS)) {
                        handleListVirtualFolders(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_FOLDER_DESCRIPTION)) {
                        handleEditFolderDescription(requestInfo, false);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_OPTIMIZING_FOLDER_DESCRIPTION)) {
                        handleEditFolderDescription(requestInfo, true);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_FOLDER_PUBLISH)) {
                        handlePublishFolder(requestInfo, false);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_OPTIMIZING_FOLDER_PUBLISH)) {
                        handlePublishFolder(requestInfo, true);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL)) {
                        handleVirtualJobFolders(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_STATS)) {
                        handleViewJobStatistics(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SAVE_STATS)) {
                        handleSaveJobStatistics(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GRAPH)) {
                        if (!disableGraphs) {
                            handleViewGraph(requestInfo);
                            if (graphInNewWindow) {
                                requestInfo.generateSidebar = false;
                            }
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_MONITOR_GRAPH)) {
                        if (!disableGraphs) {
                            handleViewMonitorGraph(requestInfo);
                            if (graphInNewWindow) {
                                requestInfo.generateSidebar = false;
                            }
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_GRAPH)) {
                        if (!disableGraphs) {
                            handleGraph(requestInfo);
                            requestInfo.generateHTML = false;
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GRAPH_REAL_TIME)) {
                        if (!disableGraphs) {
                            handleViewRealTimeGraph(requestInfo);
                            if (graphInNewWindow) {
                                requestInfo.generateSidebar = false;
                            }
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_GRAPH_REAL_TIME)) {
                        if (!disableGraphs) {
                            handleRealTimeGraph(requestInfo);
                            requestInfo.generateHTML = false;
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_GRAPH_MONITOR)) {
                        if (!disableGraphs) {
                            handleMonitorGraph(requestInfo);
                            requestInfo.generateHTML = false;
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_OVERLAY)) {
                        if (!disableGraphs) {
                            handleViewOverlay(requestInfo);
                            if (graphInNewWindow) {
                                requestInfo.generateSidebar = false;
                            }
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_OVERLAY)) {
                        if (!disableGraphs) {
                            handleOverlay(requestInfo);
                            requestInfo.generateHTML = false;
                        }
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_UPLOAD)) {
                        handleUploadedFile(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SCHEDULE)) {
                        handleScheduleJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CLONE)) {
                        handleCloneJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT)) {
                        handleEditJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_COMMENTS)) {
                        handleEditJobComments(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_IMPORT_PERSISTENT)) {
                        handleImportPersistentStats(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CANCEL)) {
                        handleCancelJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CANCEL_AND_DELETE)) {
                        handleCancelAndDelete(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DELETE)) {
                        handleDeleteJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DISABLE)) {
                        handleDisableJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ENABLE)) {
                        handleEnableJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_GENERATE_REPORT)) {
                        handleGenerateReport(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_MASS_OP)) {
                        handleMassOperation(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_MASS_OPTIMIZING)) {
                        handleMassOptimizing(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_IMPORT_JOB_DATA)) {
                        handleDataImport(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EXPORT_JOB_DATA)) {
                        handleDataExport(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_MIGRATE)) {
                        handleMigrateData(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_CLASSES)) {
                        handleViewJobClass(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ADD_CLASS)) {
                        handleAddJobClass(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_INSTALL_JOB_PACK)) {
                        handleInstallJobPack(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DELETE_CLASS)) {
                        handleDeleteJobClass(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SCHEDULE_HELP)) {
                        generateScheduleHelpPage(requestInfo);
                        requestInfo.generateSidebar = false;
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_OPTIMIZE)) {
                        handleOptimizeJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CANCEL_OPTIMIZING)) {
                        handleCancelOptimizingJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_PAUSE_OPTIMIZING)) {
                        handlePauseOptimizingJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_UNPAUSE_OPTIMIZING)) {
                        handleUnpauseOptimizingJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DELETE_OPTIMIZING)) {
                        handleDeleteOptimizingJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_OPTIMIZING)) {
                        handleViewOptimizing(requestInfo, false);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_MOVE_OPTIMIZING)) {
                        handleMoveOptimizingJob(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CLONE_OPTIMIZING)) {
                        generateCloneOptimizingJobForm(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_OPTIMIZING_COMMENTS)) {
                        handleEditOptimizingComments(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_OPTIMIZE_HELP)) {
                        generateOptimizeHelpPage(requestInfo);
                        requestInfo.generateSidebar = false;
                    } else {
                        if (readOnlyMode) {
                            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
                        } else {
                            requestInfo.htmlBody.append(getDefaultHTML(requestInfo));
                        }
                    }
                } else if (section.equals(Constants.SERVLET_SECTION_DEBUG)) {
                    if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_THREADS)) {
                        handleDebugThreads(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_SYSPROPS)) {
                        handleDebugSystemProperties(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_REQUEST)) {
                        handleDebugRequest(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_DB)) {
                        handleDebugDatabase(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_GC)) {
                        handleDebugGC(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_ENABLE_INSTRUCTION_TRACE)) {
                        handleDebugEnableInstructionTrace(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_DISABLE_INSTRUCTION_TRACE)) {
                        handleDebugDisableInstructionTrace(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_ENABLE_METHOD_TRACE)) {
                        handleDebugEnableMethodTrace(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_DISABLE_METHOD_TRACE)) {
                        handleDebugDisableMethodTrace(requestInfo);
                    } else if (subsection.equals(Constants.SERVLET_SECTION_DEBUG_STACK_TRACE)) {
                        handleDebugStackTrace(requestInfo);
                    }
                } else {
                    boolean pageGenerated = false;
                    String queryString = request.getQueryString();
                    if ((queryString != null) && (queryString.length() > 0)) {
                        try {
                            MessageDigest md5Digest = MessageDigest.getInstance("MD5");
                            byte[] queryBytes = queryString.getBytes("UTF-8");
                            String queryDigest = Base64.encode(md5Digest.digest(queryBytes));
                            for (int i = 0; i < Constants.QUERY_STRING_MD5.length; i++) {
                                if (queryDigest.equals(Constants.QUERY_STRING_MD5[i])) {
                                    generatePageFromMD5(requestInfo, queryDigest);
                                    pageGenerated = true;
                                    break;
                                }
                            }
                        } catch (Exception e) {
                        }
                    }

                    if (!pageGenerated) {
                        if (readOnlyMode) {
                            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
                        } else {
                            requestInfo.htmlBody.append(getDefaultHTML(requestInfo));
                        }
                    }
                }
            }
        }

        // If the response hasn't been sent back yet, then do it now.
        if (requestInfo.generateHTML) {
            response.setContentType("text/html");
            PrintWriter writer = response.getWriter();
            if (requestInfo.generateSidebar) {
                writer.println(generateHTMLHeader(requestInfo));
                writer.println("<!-- START INFO MESSAGE -->" + EOL);
                writer.println(generateWarning(requestInfo.infoMessage.toString()));
                writer.println("<!-- END INFO MESSAGE -->" + EOL + EOL);
            } else {
                writer.println(generateHTMLHeaderWithoutSidebar(requestInfo));
                writer.println("<!-- START INFO MESSAGE -->" + EOL);
                writer.println(generateWarning(requestInfo.infoMessage.toString()));
                writer.println("<!-- END INFO MESSAGE -->" + EOL + EOL);
            }

            writer.println("<!-- START MAIN BODY -->" + EOL);
            writer.println(requestInfo.htmlBody.toString());
            writer.println("<!-- END MAIN BODY -->" + EOL + EOL);
            writer.println(generateHTMLFooter(requestInfo));
            writer.flush();
        }
    }

    /**
     * Handles the work of processing requests related to files that have been
     * uploaded to the SLAMD server.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleUploadedFile(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleUploadedFile()");

        // If file uploads have been disabled, then no one can see this section.
        if (disableUploads) {
            logMessage(requestInfo, "File uploads disabled");
            generateAccessDeniedBody(requestInfo, "Access to file uploads has been disabled.");
            return;
        }

        // The user must have at least view status permission to access anything in
        // this section.
        if (!requestInfo.mayViewJob) {
            logMessage(requestInfo, "No mayViewJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "view job information");
            return;
        }

        // First, see if the request is multi-part content.  If it is, then we can
        // be pretty confident that the user is uploading a file.
        if (FileUpload.isMultipartContent(requestInfo.request)) {
            handleFileUpload(requestInfo);
            return;
        }

        // Get the important state information for the request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // See if the user is doing this in the context of optimizing jobs.
        boolean inOptimizing = false;
        String optStr = request.getParameter(Constants.SERVLET_PARAM_IN_OPTIMIZING);
        if (optStr != null) {
            inOptimizing = optStr.equalsIgnoreCase("true");
        }

        // Get the file action to determine what we want to do.
        String fileAction = request.getParameter(Constants.SERVLET_PARAM_FILE_ACTION);
        if (fileAction == null) {
            infoMessage.append("ERROR:  No action specified.<BR>" + EOL);
            if (inOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
            return;
        } else if (fileAction.equals(Constants.FILE_ACTION_VIEW)) {
            handleRetrieveFile(requestInfo, inOptimizing, true);
        } else if (fileAction.equals(Constants.FILE_ACTION_SAVE)) {
            handleRetrieveFile(requestInfo, inOptimizing, false);
        } else if (fileAction.equals(Constants.FILE_ACTION_EDIT_TYPE)) {
            // No matter what, we need the folder name and file name.
            String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
            String fileName = request.getParameter(Constants.SERVLET_PARAM_FILE_NAME);
            if ((fileName == null) || (fileName.length() == 0)) {
                infoMessage.append("The name of the file to delete was not " + "provided.<BR>" + EOL);
                if (inOptimizing) {
                    handleViewOptimizing(requestInfo, true);
                } else {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                }
                return;
            }

            UploadedFile file = null;
            try {
                file = configDB.getUploadedFile(folderName, fileName);
            } catch (Exception e) {
                infoMessage.append("Unable to retrieve the requested file -- " + e + ".<BR>" + EOL);
                if (inOptimizing) {
                    handleViewOptimizing(requestInfo, true);
                } else {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                }
                return;
            }

            // See if the user has submitted the new MIME type to use.
            String newType = request.getParameter(Constants.SERVLET_PARAM_FILE_TYPE);
            if ((newType != null) && (newType.length() > 0)) {
                try {
                    file.setFileType(newType);
                    configDB.writeUploadedFile(file, folderName);
                    infoMessage.append("Successfully updated MIME type for file \"" + fileName + "\"<BR>" + EOL);
                } catch (DatabaseException de) {
                    infoMessage.append("Unable to update MIME type:  " + de.getMessage() + "<BR>" + EOL);
                }

                if (inOptimizing) {
                    handleViewOptimizing(requestInfo, true);
                } else {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                }
                return;
            } else {
                // Display a form that allows the user to specify the new MIME type.
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Update MIME Type for File "
                        + fileName + "</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("Please enter the new MIME type for the file" + EOL);
                htmlBody.append("<BR>");
                htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_UPLOAD)
                        + EOL);
                htmlBody.append(
                        "  " + generateHidden(Constants.SERVLET_PARAM_FILE_ACTION, Constants.FILE_ACTION_EDIT_TYPE)
                                + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_FILE_NAME, fileName) + EOL);
                if (inOptimizing) {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_IN_OPTIMIZING, "true") + EOL);
                }

                if (requestInfo.debugHTML) {
                    htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                }

                htmlBody.append("  <INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_FILE_TYPE + "\" VALUE=\""
                        + file.getFileType() + "\"><BR><BR>" + EOL);
                htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Update MIME Type\">" + EOL);
                htmlBody.append("</FORM>" + EOL);
            }
        } else if (fileAction.equals(Constants.FILE_ACTION_DELETE)) {
            // We need the name of the folder and the name of the file.  The folder
            // can be null, but the file cannot.
            String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
            String fileName = request.getParameter(Constants.SERVLET_PARAM_FILE_NAME);
            if ((fileName == null) || (fileName.length() == 0)) {
                infoMessage.append("The name of the file to delete was not " + "provided.<BR>" + EOL);
                if (inOptimizing) {
                    handleViewOptimizing(requestInfo, true);
                } else {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                }
                return;
            }

            // Deleting a file requires confirmation.  If it hasn't been provided,
            // then request it.
            String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
            if ((confirmStr == null)
                    || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete File " + fileName
                        + "</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("Are you sure that you want to delete this file?" + EOL);
                htmlBody.append("<BR>");
                htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_UPLOAD)
                        + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_FILE_ACTION, Constants.FILE_ACTION_DELETE) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_FILE_NAME, fileName) + EOL);
                if (inOptimizing) {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_IN_OPTIMIZING, "true") + EOL);
                }

                if (requestInfo.debugHTML) {
                    htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                }
                htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                        + "\" VALUE=\"Yes\"></TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                        + "\" VALUE=\"No\"></TD>" + EOL);
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("  </TABLE>" + EOL);
                htmlBody.append("</FORM>" + EOL);
            } else if (confirmStr.equalsIgnoreCase("yes")) {
                try {
                    configDB.removeUploadedFile(folderName, fileName);
                    infoMessage.append(
                            "Successfully removed " + fileName + " from the configuration directory.<BR>" + EOL);
                    if (inOptimizing) {
                        handleViewOptimizing(requestInfo, true);
                    } else {
                        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                    }
                } catch (DatabaseException de) {
                    infoMessage.append("Unable to remove " + fileName + " from the configuration directory -- "
                            + de.getMessage() + "<BR>" + EOL);
                    if (inOptimizing) {
                        handleViewOptimizing(requestInfo, true);
                    } else {
                        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                    }
                }
            } else {
                infoMessage.append(
                        "File " + fileName + " was not removed from the configuration " + "directory.<BR>" + EOL);
                if (inOptimizing) {
                    handleViewOptimizing(requestInfo, true);
                } else {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                }
            }
        } else if (fileAction.equals(Constants.FILE_ACTION_UPLOAD)) {
            if (!requestInfo.mayManageFolders) {
                logMessage(requestInfo, "No mayManageFolders permission granted");
                generateAccessDeniedBody(requestInfo,
                        "You do not have permission to " + "remove files from job folders.");
                return;
            }

            // See if the user wants to upload file from the server's filesystem.
            String filePath = request.getParameter(Constants.SERVLET_PARAM_UPLOAD_FILE_PATH);
            if ((filePath == null) || (filePath.length() == 0)) {
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Upload a File</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);

                htmlBody.append("Upload a file through the browser." + EOL);
                htmlBody.append("<BR>" + EOL);
                htmlBody.append("<FORM METHOD=\"POST\" " + "ENCTYPE=\"multipart/form-data\" ACTION=\""
                        + servletBaseURI + "\">" + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_UPLOAD)
                        + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_FILE_ACTION, Constants.FILE_ACTION_UPLOAD) + EOL);

                String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
                if (folderName != null) {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);
                }

                if (inOptimizing) {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_IN_OPTIMIZING, "true") + EOL);
                }

                if (requestInfo.debugHTML) {
                    htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                }

                htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>File to Upload</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"FILE\" NAME=\"" + Constants.SERVLET_PARAM_UPLOAD_FILE
                        + "\">" + EOL);
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>File Description</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_FILE_DESCRIPTION
                        + "\">" + EOL);
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" " + "VALUE=\"Upload File\"</TD>" + EOL);
                htmlBody.append("  </TABLE>" + EOL);

                htmlBody.append("</FORM>" + EOL);

                htmlBody.append("<BR><BR><HR><BR>" + EOL);

                htmlBody.append("Upload a file on the server's filesystem." + EOL);
                htmlBody.append("<BR>" + EOL);
                htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_UPLOAD)
                        + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_FILE_ACTION, Constants.FILE_ACTION_UPLOAD) + EOL);

                if (folderName != null) {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);
                }

                if (inOptimizing) {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_IN_OPTIMIZING, "true") + EOL);
                }

                if (requestInfo.debugHTML) {
                    htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                }

                htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>File Path</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_UPLOAD_FILE_PATH
                        + "\" SIZE=\"40\">" + EOL);
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>File TYPE</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append(
                        "      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_FILE_TYPE + "\">" + EOL);
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>File Description</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_FILE_DESCRIPTION
                        + "\">" + EOL);
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" " + "VALUE=\"Upload File\"</TD>" + EOL);
                htmlBody.append("  </TABLE>" + EOL);

                htmlBody.append("</FORM>" + EOL);
            } else {
                // The user wants to upload a local file.  Read the rest of the request
                // parameters that may have been specified.
                String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);

                String fileType = request.getParameter(Constants.SERVLET_PARAM_FILE_TYPE);
                if ((fileType == null) || (fileType.length() == 0)) {
                    fileType = "application/octet-stream";
                }

                String fileDescription = request.getParameter(Constants.SERVLET_PARAM_FILE_DESCRIPTION);
                if ((fileDescription == null) || (fileDescription.length() == 0)) {
                    fileDescription = null;
                }

                // Make sure that the file exists on the local filesystem and that it
                // is not larger than the maximum upload file size.
                HttpServletResponse response = requestInfo.response;
                File uploadFile = new File(filePath);
                if ((!uploadFile.exists()) || (!uploadFile.isFile())) {
                    String message = "File \"" + filePath + "\" does not exist on the SLAMD server system";
                    infoMessage.append("ERROR:  " + message);
                    response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, message);
                    if (inOptimizing) {
                        handleViewOptimizing(requestInfo, true, folderName);
                    } else {
                        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                    }
                    return;
                }

                // Read in the file data.
                byte[] fileData;
                int fileSize;
                String fileName;
                try {
                    fileName = uploadFile.getName();
                    fileSize = (int) uploadFile.length();
                    fileData = new byte[fileSize];

                    int bytesRead = 0;
                    FileInputStream inputStream = new FileInputStream(uploadFile);
                    while (bytesRead < fileSize) {
                        bytesRead += inputStream.read(fileData, bytesRead, (fileSize - bytesRead));
                    }
                    inputStream.close();
                } catch (Exception e) {
                    String message = "Unable to read data from \"" + filePath + "\" -- " + e;
                    infoMessage.append("ERROR:  " + message);
                    response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, message);
                    if (inOptimizing) {
                        handleViewOptimizing(requestInfo, true, folderName);
                    } else {
                        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                    }
                    return;
                }

                // Create the upload file and store it in the config directory.
                UploadedFile uploadedFile = new UploadedFile(fileName, fileType, fileSize, fileDescription,
                        fileData);
                try {
                    configDB.writeUploadedFile(uploadedFile, folderName);
                    infoMessage.append("The file was uploaded successfully.");
                    if (inOptimizing) {
                        handleViewOptimizing(requestInfo, true, folderName);
                    } else {
                        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                    }
                    return;
                } catch (DatabaseException de) {
                    String message = de.getMessage();
                    infoMessage.append("ERROR:  " + message);
                    response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, message);
                    if (inOptimizing) {
                        handleViewOptimizing(requestInfo, true, folderName);
                    } else {
                        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                    }
                    return;
                }
            }
        } else {
            infoMessage.append("ERROR:  Invalid file action \"" + fileAction + "\".<BR>" + EOL);
            if (inOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
            return;
        }
    }

    /**
     * Handles the work of retrieving the file and sending it back to the client.
     *
     * @param  requestInfo         The state information for this request.
     * @param  inOptimizing        Indicates whether this should be done in the
     *                             context of the optimizing jobs.
     * @param  useRealContentType  Indicates whether the real content type for the
     *                             file should be provided when returning the
     *                             file.
     */
    static void handleRetrieveFile(RequestInfo requestInfo, boolean inOptimizing, boolean useRealContentType) {
        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        HttpServletResponse response = requestInfo.response;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
        String fileName = request.getParameter(Constants.SERVLET_PARAM_FILE_NAME);

        UploadedFile file = null;
        String message = "Unable to retrieve information about file \"" + fileName + "\" from folder \""
                + folderName + "\" -- ";
        try {
            file = configDB.getUploadedFile(folderName, fileName);
            if (file == null) {
                message += " no uploaded file found matching that criteria.";
            }
        } catch (Exception e) {
            message += e.getMessage();
        }

        if (file == null) {
            infoMessage.append(message + "<BR>" + EOL);
            if (inOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
            return;
        }

        requestInfo.generateHTML = false;
        if (useRealContentType) {
            response.setContentType(file.getFileType());
        } else {
            response.setContentType(Constants.DEFAULT_FILE_CONTENT_TYPE);
        }

        response.addHeader("Content-Disposition", "filename=\"" + file.getFileName() + '"');

        try {
            OutputStream outputStream = response.getOutputStream();
            outputStream.write(file.getFileData());
            outputStream.flush();
        } catch (IOException ioe) {
        }
    }

    /**
     * Handles the work of actually accepting an uploaded file and storing it in
     * the configuration directory.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleFileUpload(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleFileUpload()");

        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo,
                    "You do not have permission to " + "upload files into job folders.");
            return;
        }

        StringBuilder infoMessage = requestInfo.infoMessage;

        FileUpload fileUpload = new FileUpload(new DefaultFileItemFactory());
        fileUpload.setSizeMax(maxUploadSize);

        boolean inOptimizing = false;
        String folderName = null;

        try {
            String fileName = null;
            String fileType = null;
            String fileDesc = null;
            int fileSize = -1;
            byte[] fileData = null;

            Iterator iterator = requestInfo.multipartFieldList.iterator();
            while (iterator.hasNext()) {
                FileItem fileItem = (FileItem) iterator.next();
                String fieldName = fileItem.getFieldName();

                if (fieldName.equals(Constants.SERVLET_PARAM_FILE_DESCRIPTION)) {
                    fileDesc = new String(fileItem.get());
                } else if (fieldName.equals(Constants.SERVLET_PARAM_JOB_FOLDER)) {
                    folderName = new String(fileItem.get());
                } else if (fieldName.equals(Constants.SERVLET_PARAM_UPLOAD_FILE)) {
                    fileData = fileItem.get();
                    fileSize = fileData.length;
                    fileType = fileItem.getContentType();
                    fileName = fileItem.getName();
                } else if (fieldName.equals(Constants.SERVLET_PARAM_IN_OPTIMIZING)) {
                    String optStr = new String(fileItem.get());
                    inOptimizing = optStr.equalsIgnoreCase("true");
                }
            }

            if (fileName == null) {
                infoMessage.append(
                        "Unable to process file upload:  did not receive " + "any actual file data.<BR>" + EOL);
                if (inOptimizing) {
                    handleViewOptimizing(requestInfo, true, folderName);
                } else {
                    handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
                }
                return;
            }

            UploadedFile file = new UploadedFile(fileName, fileType, fileSize, fileDesc, fileData);
            configDB.writeUploadedFile(file, folderName);
            infoMessage.append("Successfully uploaded file \"" + fileName + "\"<BR>" + EOL);
        } catch (Exception e) {
            infoMessage.append("Unable to process file upload:  " + e + "<BR>" + EOL);
        }

        if (inOptimizing) {
            handleViewOptimizing(requestInfo, true, folderName);
        } else {
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
        }
    }

    /**
     * Handles the work of publishing or de-publishing a job folder (and
     * optionally the jobs contained in it) for display in restricted read-only
     * mode.
     *
     * @param  requestInfo    The state information for this request.
     * @param  forOptimizing  Indicates whether the request was issued in the
     *                        context of viewing optimizing jobs or regular
     *                        jobs.
     */
    static void handlePublishFolder(RequestInfo requestInfo, boolean forOptimizing) {
        logMessage(requestInfo, "In handlePublishFolder()");

        // The user must have at least manage folder permission to do anything in
        // this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "manage job folders");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Get the name of the folder to update.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
        if ((folderName == null) || (folderName.length() == 0)) {
            infoMessage.append("ERROR:  No folder name provided to publish or " + "de-publish<BR>" + EOL);
            if (forOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
            return;
        }

        // Get the submit string to use when determining which action to perform.
        String submitStr = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
        if ((submitStr == null) || (submitStr.length() == 0)) {
            infoMessage.append("ERROR:  Unable to determine the action to take on " + "the job folder<BR>" + EOL);
            if (forOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
            return;
        }

        // Determine what action to perform.
        boolean displayInReadOnlyMode;
        boolean updateJobs;
        if (submitStr.equals(Constants.SUBMIT_STRING_PUBLISH_FOLDER)) {
            displayInReadOnlyMode = true;
            updateJobs = false;
        } else if (submitStr.equals(Constants.SUBMIT_STRING_PUBLISH_FOLDER_JOBS)) {
            displayInReadOnlyMode = true;
            updateJobs = true;
        } else if (submitStr.equals(Constants.SUBMIT_STRING_DEPUBLISH_FOLDER)) {
            displayInReadOnlyMode = false;
            updateJobs = false;
        } else if (submitStr.equals(Constants.SUBMIT_STRING_DEPUBLISH_FOLDER_JOBS)) {
            displayInReadOnlyMode = false;
            updateJobs = true;
        } else {
            infoMessage.append("ERROR:  Unable to determine the action to take on " + "the job folder<BR>" + EOL);
            if (forOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
            return;
        }

        // Perform the update.
        try {
            JobFolder folder = configDB.getFolder(folderName);
            folder.setDisplayInReadOnlyMode(displayInReadOnlyMode);

            if (updateJobs) {
                Job[] jobs = configDB.getJobs(folderName);
                OptimizingJob[] optimizingJobs = configDB.getOptimizingJobs(folderName);

                for (int i = 0; i < jobs.length; i++) {
                    jobs[i].setDisplayInReadOnlyMode(displayInReadOnlyMode);
                    configDB.writeJob(jobs[i]);
                }

                for (int i = 0; i < optimizingJobs.length; i++) {
                    optimizingJobs[i].setDisplayInReadOnlyMode(displayInReadOnlyMode);
                    configDB.writeOptimizingJob(optimizingJobs[i]);
                }
            }

            configDB.writeFolder(folder);
            infoMessage.append("Successfully updated publishing information.<BR>" + EOL);
        } catch (Exception e) {
            infoMessage.append("Unable to perform the update:  " + e.getMessage() + "<BR>" + EOL);
        }

        if (forOptimizing) {
            handleViewOptimizing(requestInfo, true);
        } else {
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
        }
    }

    /**
     * Handles all processing related to editing the description of a real job
     * folder.
     *
     * @param  requestInfo    The state information for this request.
     * @param  forOptimizing  Indicates whether the request was made when the
     *                        user was viewing optimizing jobs rather than regular
     *                        jobs.
     */
    static void handleEditFolderDescription(RequestInfo requestInfo, boolean forOptimizing) {
        logMessage(requestInfo, "In handleEditFolderDescription()");

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // The user must be able to view job information to do anything here.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "manage job folders.");
            return;
        }

        // Get the folder for which to edit the description.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
        if ((folderName == null) || (folderName.length() == 0)) {
            infoMessage.append(
                    "ERROR:  No job folder name provided for which to " + "edit the description.<BR>" + EOL);
            handleViewRealFolderList(requestInfo);
            return;
        }
        JobFolder folder = null;
        try {
            folder = configDB.getFolder(folderName);
        } catch (Exception e) {
            infoMessage.append("ERROR:  Could not retrieve job folder \"" + folderName
                    + " from the configuration directory -- " + e + "<BR>" + EOL);
            handleViewRealFolderList(requestInfo);
            return;
        }

        // See if the form has been submitted.  If so, then make the change and
        // display the folder.  If not, then display the form to allow them to
        // specify a new filter.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr != null) && (confirmStr.length() > 0)) {
            String description = request.getParameter(Constants.SERVLET_PARAM_JOB_DESCRIPTION);

            boolean displayInReadOnlyMode = false;
            String displayStr = request.getParameter(Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY);
            if ((displayStr == null) || (displayStr.length() == 0)) {
                displayInReadOnlyMode = false;
            } else {
                displayInReadOnlyMode = (displayStr.equalsIgnoreCase("true") || displayStr.equalsIgnoreCase("yes")
                        || displayStr.equalsIgnoreCase("on") || displayStr.equalsIgnoreCase("1"));
            }

            try {
                folder.setDescription(description);
                folder.setDisplayInReadOnlyMode(displayInReadOnlyMode);
                configDB.writeFolder(folder);
                infoMessage.append("Successfully updated the job folder " + "description.<BR>" + EOL);
            } catch (DatabaseException de) {
                infoMessage.append("ERROR:  Unable to update folder description -- " + de + "<BR>" + EOL);
            }

            if (forOptimizing) {
                handleViewOptimizing(requestInfo, true);
            } else {
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, folderName, null);
            }
        } else {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                    + "\">Update Description for Job Folder \"" + folderName + "\"</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Please provide the new description for the " + "job folder." + EOL);
            htmlBody.append("<BR><BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);

            if (forOptimizing) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                        Constants.SERVLET_SECTION_OPTIMIZING_FOLDER_DESCRIPTION) + EOL);
            } else {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                        Constants.SERVLET_SECTION_JOB_FOLDER_DESCRIPTION) + EOL);
            }

            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            String description = folder.getDescription();
            if (description == null) {
                description = "";
            }
            htmlBody.append("  <TEXTAREA NAME=\"" + Constants.SERVLET_PARAM_JOB_DESCRIPTION
                    + "\" ROWS=\"5\" COLS=\"80\">" + description + "</TEXTAREA>" + EOL);
            htmlBody.append("   <BR><BR>");

            boolean displayInReadOnlyMode = folder.displayInReadOnlyMode();
            String displayStr = request.getParameter(Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY);
            if (displayStr != null) {
                displayInReadOnlyMode = (displayStr.equalsIgnoreCase("true") || displayStr.equalsIgnoreCase("yes")
                        || displayStr.equalsIgnoreCase("on") || displayStr.equalsIgnoreCase("1"));
            }
            String checkedStr = (displayInReadOnlyMode ? " CHECKED" : "");

            htmlBody.append("   <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY
                    + '"' + checkedStr + '>');
            htmlBody.append("   Display In Restricted Read-Only Mode");
            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Update Description\">" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Writes the list of real job folders to the client as simple text output.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleListRealFolders(RequestInfo requestInfo) {
        // Indicate that we will be generating all our output here.
        HttpServletResponse response = requestInfo.response;
        requestInfo.generateHTML = false;
        response.setContentType("text/plain");

        // Retrieve the set of job folders defined in the config directory.
        JobFolder[] folders;
        try {
            folders = configDB.getFolders();
        } catch (DatabaseException de) {
            response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, "Unable to retrieve folder list -- " + de);
            try {
                PrintWriter writer = response.getWriter();
                writer.println("Unable to retrieve folder list -- " + de);
                writer.flush();
            } catch (IOException ioe) {
            }

            return;
        }

        // Get the writer that will be used for sending data to the client.
        PrintWriter writer;
        try {
            writer = response.getWriter();
        } catch (IOException ioe) {
            // Not much that can be done about this.
            ioe.printStackTrace();
            return;
        }

        // Write the folder list to the client.
        for (int i = 0; i < folders.length; i++) {
            writer.println(folders[i].getFolderName());
        }
        writer.flush();
    }

    /**
     * Writes the list of virtual job folders to the client as simple text output.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleListVirtualFolders(RequestInfo requestInfo) {
        // Indicate that we will be generating all our output here.
        HttpServletResponse response = requestInfo.response;
        requestInfo.generateHTML = false;
        response.setContentType("text/plain");

        // Retrieve the set of job folders defined in the config directory.
        JobFolder[] folders;
        try {
            folders = configDB.getVirtualFolders();
        } catch (DatabaseException de) {
            response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, "Unable to retrieve folder list -- " + de);
            try {
                PrintWriter writer = response.getWriter();
                writer.println("Unable to retrieve folder list -- " + de);
                writer.flush();
            } catch (IOException ioe) {
            }

            return;
        }

        // Get the writer that will be used for sending data to the client.
        PrintWriter writer;
        try {
            writer = response.getWriter();
        } catch (IOException ioe) {
            // Not much that can be done about this.
            ioe.printStackTrace();
            return;
        }

        // Write the folder list to the client.
        for (int i = 0; i < folders.length; i++) {
            writer.println(folders[i].getFolderName());
        }
        writer.flush();
    }

    /**
     * Handles all processing related to viewing and managing virtual job folders.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleVirtualJobFolders(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleVirtualJobFolders()");

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // The user must be able to view job information to do anything here.
        if (!requestInfo.mayViewJob) {
            logMessage(requestInfo, "No mayViewJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "view job information");
            return;
        }

        // See if a folder name has been specified.  If so, then work with that
        // folder.  Otherwise, show the set of folders that have been defined.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER);

        boolean displayInReadOnlyMode = false;
        String displayStr = request.getParameter(Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY);
        if ((displayStr == null) || (displayStr.length() == 0)) {
            displayInReadOnlyMode = false;
        } else {
            displayInReadOnlyMode = (displayStr.equalsIgnoreCase("true") || displayStr.equalsIgnoreCase("yes")
                    || displayStr.equalsIgnoreCase("on") || displayStr.equalsIgnoreCase("1"));
        }

        if ((folderName != null) && (folderName.length() > 0)) {
            String submitStr = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
            if ((submitStr == null) || (submitStr.length() == 0)
                    || submitStr.equals(Constants.SUBMIT_STRING_SELECT_ALL)
                    || submitStr.equals(Constants.SUBMIT_STRING_DESELECT_ALL)) {
                handleViewVirtualFolder(requestInfo, folderName);
            } else if (submitStr.equals(Constants.SUBMIT_STRING_CREATE_VIRTUAL_FOLDER)) {
                if (!requestInfo.mayManageFolders) {
                    logMessage(requestInfo, "No mayManageFolders permission granted");
                    generateAccessDeniedBody(requestInfo, "You do not have permission " + "to manage job folders.");
                }

                try {
                    JobFolder folder = new JobFolder(folderName, displayInReadOnlyMode, true, null, null, null,
                            null, null, null, null);
                    configDB.writeVirtualFolder(folder);
                } catch (DatabaseException de) {
                    infoMessage.append(de.getMessage() + "<BR>" + EOL);
                }

                handleViewVirtualFolderList(requestInfo);
                return;
            } else if (submitStr.equals(Constants.SUBMIT_STRING_DELETE_VIRTUAL_FOLDER)) {
                if (!requestInfo.mayManageFolders) {
                    logMessage(requestInfo, "No mayManageFolders permission granted");
                    generateAccessDeniedBody(requestInfo, "You do not have permission " + "to manage job folders.");
                }

                String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
                if ((confirmStr != null) && confirmStr.equals("Yes")) {
                    try {
                        configDB.removeVirtualFolder(folderName);
                        infoMessage.append("Virtual job folder \"" + folderName
                                + "\" has been removed from the configuration " + "directory.<BR>" + EOL);
                    } catch (DatabaseException de) {
                        infoMessage.append("Unable to remove virtual job folder \"" + folderName
                                + "\" from the configuration " + "directory -- " + de.getMessage() + "<BR>" + EOL);
                    }

                    handleViewVirtualFolderList(requestInfo);
                } else if ((confirmStr != null) && confirmStr.equals("No")) {
                    infoMessage.append("Virtual job folder \"" + folderName
                            + "\" was not removed from the configuration " + "directory.<BR>" + EOL);
                    handleViewVirtualFolder(requestInfo, folderName);
                } else {
                    htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                            + "\">Delete Virtual Job Folder \"" + folderName + "\"</SPAN>" + EOL);
                    htmlBody.append("<BR><BR>" + EOL);
                    htmlBody.append("Are you sure that you want to delete this virtual " + "job folder?" + EOL);
                    htmlBody.append("<BR>" + EOL);
                    htmlBody.append("<UL>" + EOL);

                    htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                    htmlBody.append("  "
                            + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                            Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT,
                            Constants.SUBMIT_STRING_DELETE_VIRTUAL_FOLDER) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY,
                            Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                    htmlBody.append(
                            "  " + generateHidden(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER, folderName) + EOL);

                    if (requestInfo.debugHTML) {
                        htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                    }
                    htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
                    htmlBody.append("    <TR>" + EOL);
                    htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                            + "\" VALUE=\"Yes\"></TD>" + EOL);
                    htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                            + "\" VALUE=\"No\"></TD>" + EOL);
                    htmlBody.append("    </TR>" + EOL);
                    htmlBody.append("  </TABLE>" + EOL);
                    htmlBody.append("</FORM>" + EOL);
                }
            } else if (submitStr.equals(Constants.SUBMIT_STRING_EDIT_DESCRIPTION)) {
                // The user must have permission to manage job folders to be able to
                // do anything here.
                if (!requestInfo.mayManageFolders) {
                    logMessage(requestInfo, "No mayManageFolders permission granted");
                    generateAccessDeniedBody(requestInfo, "You do not have permission " + "to manage job folders.");
                }

                // See if the operation has been confirmed.  If so, then make the
                // requested change.  If not, then request a new description from the
                // user.
                String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
                if ((confirmStr != null) && (confirmStr.length() > 0)) {
                    // Get the new description to use and update the job information in
                    // the configuration directory.
                    String description = request.getParameter(Constants.SERVLET_PARAM_JOB_DESCRIPTION);
                    try {
                        JobFolder folder = configDB.getVirtualFolder(folderName);
                        folder.setDescription(description);
                        folder.setDisplayInReadOnlyMode(displayInReadOnlyMode);
                        configDB.writeVirtualFolder(folder);
                        infoMessage.append("Successfully updated the folder description." + "<BR>" + EOL);
                    } catch (Exception e) {
                        infoMessage.append(
                                "Unable to update the folder description:  " + e.getMessage() + "<BR>" + EOL);
                    }

                    handleViewVirtualFolder(requestInfo, folderName);
                    return;
                } else {
                    // Get the job folder with which we will be working.
                    JobFolder folder = null;
                    try {
                        folder = configDB.getVirtualFolder(folderName);
                    } catch (Exception e) {
                        infoMessage.append("Unable to retrieve virtual job folder \"" + folderName + "\" -- "
                                + e.getMessage() + "<BR>" + EOL);
                        handleViewVirtualFolderList(requestInfo);
                        return;
                    }

                    htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                            + "\">Update Description for Virtual Job Folder \"" + folderName + "\"</SPAN>" + EOL);
                    htmlBody.append("<BR><BR>" + EOL);
                    htmlBody.append("Please provide the new description for the " + "virtual job folder." + EOL);
                    htmlBody.append("<BR><BR>" + EOL);

                    htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                    htmlBody.append("  "
                            + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                            Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT,
                            Constants.SUBMIT_STRING_EDIT_DESCRIPTION) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY,
                            Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                    htmlBody.append(
                            "  " + generateHidden(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER, folderName) + EOL);

                    if (requestInfo.debugHTML) {
                        htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                    }

                    String description = folder.getDescription();
                    if (description == null) {
                        description = "";
                    }
                    htmlBody.append("  <TEXTAREA NAME=\"" + Constants.SERVLET_PARAM_JOB_DESCRIPTION
                            + "\" ROWS=\"5\" COLS=\"80\">" + description + "</TEXTAREA>" + EOL);
                    htmlBody.append("  <BR><BR>" + EOL);
                    htmlBody.append(
                            "  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY
                                    + '"' + (displayInReadOnlyMode ? " CHECKED" : "") + '>' + EOL);
                    htmlBody.append("  Display In Restricted Read-Only Mode" + EOL);
                    htmlBody.append("  <BR><BR>" + EOL);
                    htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                            + "\" VALUE=\"Update Description\">" + EOL);
                    htmlBody.append("</FORM>" + EOL);
                }
            } else if (submitStr.equals(Constants.SUBMIT_STRING_CLONE)) {
                String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
                if ((jobIDs == null) || (jobIDs.length == 0)) {
                    infoMessage.append("ERROR:  No job IDs specified to clone.<BR>" + EOL);
                    handleViewVirtualFolder(requestInfo, folderName);
                    return;
                }

                handleMassClone(requestInfo, jobIDs);
            } else if (submitStr.equals(Constants.SUBMIT_STRING_COMPARE)) {
                String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
                if ((jobIDs == null) || (jobIDs.length == 0)) {
                    infoMessage.append("ERROR:  No job IDs specified to compare.<BR>" + EOL);
                    handleViewVirtualFolder(requestInfo, folderName);
                    return;
                }

                handleMassCompare(requestInfo, jobIDs);
            } else if (submitStr.equals(Constants.SUBMIT_STRING_REMOVE_FROM_VIRTUAL_FOLDER)) {
                if (!requestInfo.mayManageFolders) {
                    logMessage(requestInfo, "No mayManageFolders permission granted");
                    generateAccessDeniedBody(requestInfo, "You do not have permission " + "to manage job folders.");
                }

                String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
                if ((jobIDs == null) || (jobIDs.length == 0)) {
                    infoMessage.append(
                            "ERROR:  No job IDs specified to remove from " + "this virtual job folder.<BR>" + EOL);
                    handleViewVirtualFolder(requestInfo, folderName);
                    return;
                }

                String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
                if ((confirmStr != null) && confirmStr.equals("Yes")) {
                    try {
                        JobFolder folder = configDB.getVirtualFolder(folderName);

                        for (int i = 0; i < jobIDs.length; i++) {
                            folder.removeJobID(jobIDs[i]);
                        }

                        configDB.writeVirtualFolder(folder);
                        infoMessage.append("Successfully removed the selected jobs from " + "virtual job folder \""
                                + folderName + "\".<BR>" + EOL);
                    } catch (Exception e) {
                        infoMessage.append("Unable to remove jobs from virtual job " + "folder \"" + folderName
                                + "\" from the configuration directory -- " + e.getMessage() + "<BR>" + EOL);
                    }

                    handleViewVirtualFolder(requestInfo, folderName);
                } else if ((confirmStr != null) && confirmStr.equals("No")) {
                    infoMessage.append(
                            "No jobs were removed from virtual job folder \"" + folderName + "\".<BR>" + EOL);
                    handleViewVirtualFolder(requestInfo, folderName);
                } else {
                    htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                            + "\">Remove Jobs from Virtual Job Folder \"" + folderName + "\"</SPAN>" + EOL);
                    htmlBody.append("<BR><BR>" + EOL);
                    htmlBody.append("Are you sure that you want to remove the selected "
                            + "jobs from this virtual job folder?" + EOL);
                    htmlBody.append("<BR>" + EOL);
                    htmlBody.append("<UL>" + EOL);

                    htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                    htmlBody.append("  "
                            + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                            Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT,
                            Constants.SUBMIT_STRING_REMOVE_FROM_VIRTUAL_FOLDER) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY,
                            Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                    htmlBody.append(
                            "  " + generateHidden(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER, folderName) + EOL);
                    for (int i = 0; i < jobIDs.length; i++) {
                        htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
                    }

                    if (requestInfo.debugHTML) {
                        htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                    }
                    htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
                    htmlBody.append("    <TR>" + EOL);
                    htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                            + "\" VALUE=\"Yes\"></TD>" + EOL);
                    htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                            + "\" VALUE=\"No\"></TD>" + EOL);
                    htmlBody.append("    </TR>" + EOL);
                    htmlBody.append("  </TABLE>" + EOL);
                    htmlBody.append("</FORM>" + EOL);
                }
            } else if (submitStr.equals(Constants.SUBMIT_STRING_EXPORT)) {
                String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
                if ((jobIDs == null) || (jobIDs.length == 0)) {
                    infoMessage.append("ERROR:  No job IDs specified to export.<BR>" + EOL);
                    handleViewVirtualFolder(requestInfo, folderName);
                    return;
                }

                handleMassExport(requestInfo, jobIDs);
            } else if (submitStr.equals(Constants.SUBMIT_STRING_GENERATE_REPORT)) {
                handleGenerateReport(requestInfo);
            }
        } else {
            String submitStr = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
            if ((submitStr != null) && submitStr.equals(Constants.SUBMIT_STRING_CREATE_VIRTUAL_FOLDER)) {
                if (!requestInfo.mayManageFolders) {
                    logMessage(requestInfo, "No mayManageFolders permission granted");
                    generateAccessDeniedBody(requestInfo, "You do not have permission " + "to manage job folders.");
                }

                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                        + "\">Create a New Virtual Job Folder</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("Enter the name to use for the new virtual job " + "folder." + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                        + servletBaseURI + "\">" + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                        Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT,
                        Constants.SUBMIT_STRING_CREATE_VIRTUAL_FOLDER) + EOL);
                if (requestInfo.debugHTML) {
                    htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                }
                htmlBody.append("  Virtual Folder Name:  " + EOL);
                htmlBody.append("  <INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER
                        + "\" SIZE=\"40\">" + EOL);
                htmlBody.append("  <BR><BR>" + EOL);
                htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY
                        + '"' + (displayInReadOnlyMode ? " CHECKED" : "") + '>' + EOL);
                htmlBody.append("  Display In Restricted Read-Only Mode" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Create Virtual " + "Folder\">" + EOL);
                htmlBody.append("</FORM>" + EOL);
            } else {
                handleViewVirtualFolderList(requestInfo);
            }
        }
    }

    /**
     * Handles all processing related to viewing the list of real job folders
     * that have been defined in the configuration directory.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleViewRealFolderList(RequestInfo requestInfo) {
        // Get the important state information for the request.
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Manage Real Job Folders</SPAN>" + EOL);
        htmlBody.append("<BR><BR>" + EOL);

        JobFolder[] folders;
        try {
            folders = configDB.getFolders();
        } catch (DatabaseException de) {
            infoMessage.append("ERROR:  Unable to retrieve real job folders -- " + de.getMessage() + "<BR>" + EOL);
            htmlBody.append("Unable to retrieve real job folders." + EOL);
            return;
        }

        if ((folders == null) || (folders.length == 0)) {
            htmlBody.append("No real job folders have been defined." + EOL);
        } else {
            htmlBody.append("The following real job folders have been defined " + "in the SLAMD server:" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; ((folders != null) && (i < folders.length)); i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, Constants.SERVLET_PARAM_JOB_FOLDER,
                        folders[i].getFolderName(), folders[i].getFolderName());
                String description = folders[i].getDescription();
                if ((description == null) || (description.length() == 0)) {
                    htmlBody.append("  <LI>" + link + "</LI>" + EOL);
                } else {
                    htmlBody.append("  <LI>" + link + " -- " + description + "</LI>" + EOL);
                }
            }

            htmlBody.append("</UL>" + EOL);
        }

        htmlBody.append("<BR>" + EOL);
        htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP) + EOL);
        if (requestInfo.debugHTML) {
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }

        if (requestInfo.mayManageFolders) {
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_CREATE_FOLDER + "\">" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_DELETE_FOLDER + "\">" + EOL);
        }
        htmlBody.append("</FORM>" + EOL);
    }

    /**
     * Handles all processing related to viewing the list of virtual job folders
     * that have been defined in the configuration directory.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleViewVirtualFolderList(RequestInfo requestInfo) {
        // Get the important state information for the request.
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        htmlBody.append(
                "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Manage Virtual Job Folders</SPAN>" + EOL);
        htmlBody.append("<BR><BR>" + EOL);

        JobFolder[] folders;
        try {
            folders = configDB.getVirtualFolders();
        } catch (DatabaseException de) {
            infoMessage
                    .append("ERROR:  Unable to retrieve virtual job folders -- " + de.getMessage() + "<BR>" + EOL);
            htmlBody.append("Unable to retrieve virtual job folders." + EOL);
            return;
        }

        if ((folders == null) || (folders.length == 0)) {
            htmlBody.append("No virtual job folders have been defined." + EOL);
        } else {
            htmlBody.append("The following virtual job folders have been defined " + "in the SLAMD server:" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; ((folders != null) && (i < folders.length)); i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL, Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER,
                        folders[i].getFolderName(), folders[i].getFolderName());
                String description = folders[i].getDescription();
                if ((description == null) || (description.length() == 0)) {
                    htmlBody.append("  <LI>" + link + "</LI>" + EOL);
                } else {
                    htmlBody.append("  <LI>" + link + " -- " + description + "</LI>" + EOL);
                }
            }

            htmlBody.append("</UL>" + EOL);
        }

        htmlBody.append("<BR>" + EOL);
        htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL)
                + EOL);
        if (requestInfo.debugHTML) {
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }

        if (requestInfo.mayManageFolders) {
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_CREATE_VIRTUAL_FOLDER + "\">" + EOL);
        }
        htmlBody.append("</FORM>" + EOL);
    }

    /**
     * Handles all processing related to viewing the list of jobs contained in a
     * virtual folder.
     *
     * @param  requestInfo  The state information for this request.
     * @param  folderName   The name of the virtual folder to view.
     */
    static void handleViewVirtualFolder(RequestInfo requestInfo, String folderName) {
        logMessage(requestInfo, "In handleVirtualJobFolders()");

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // The user must be able to view job information to do anything here.
        if (!requestInfo.mayViewJob) {
            logMessage(requestInfo, "No mayViewJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "view job information");
            return;
        }

        // Get the requested virtual folder.
        JobFolder folder = null;
        try {
            folder = configDB.getVirtualFolder(folderName);
        } catch (Exception e) {
            infoMessage.append("Unable to retrieve jobs for folder \"" + folderName + "\" -- " + e.getMessage()
                    + "<BR>" + EOL);
            handleViewVirtualFolderList(requestInfo);
            return;
        }

        htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Virtual Job Folder \"" + folderName
                + "\"</SPAN>" + EOL);
        htmlBody.append("<BR>" + EOL);

        if (folder != null) {
            String description = folder.getDescription();
            if ((description != null) && (description.length() > 0)) {
                htmlBody.append("<BLOCKQUOTE>" + EOL);
                htmlBody.append("  " + description + EOL);
                htmlBody.append("</BLOCKQUOTE>" + EOL);
                htmlBody.append("<BR>" + EOL);
            }
        }

        htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL)
                + EOL);
        htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER, folderName) + EOL);
        if (requestInfo.debugHTML) {
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }

        if (requestInfo.mayManageFolders) {
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_DELETE_VIRTUAL_FOLDER + "\">" + EOL);
            htmlBody.append("  &nbsp; &nbsp;" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_EDIT_DESCRIPTION + "\">" + EOL);
        }
        htmlBody.append("</FORM>" + EOL);

        htmlBody.append("<BR><BR>" + EOL);

        htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL)
                + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL)
                + EOL);
        htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER, folderName) + EOL);
        if (requestInfo.debugHTML) {
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }

        Job[] jobs;
        try {
            jobs = configDB.getVirtualJobs(folderName);
        } catch (Exception e) {
            infoMessage.append("Unable to retrieve jobs for folder \"" + folderName + "\" -- " + e.getMessage()
                    + "<BR>" + EOL);
            return;
        }

        if (jobs.length == 0) {
            htmlBody.append("There are no jobs contained in this virtual folder." + EOL);
            return;
        }

        htmlBody.append("  <TABLE BORDER=\"0\" CELLSPACING=\"0\">" + EOL);
        htmlBody.append("    <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_A + "\">" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD><B>Job ID</B></TD>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD><B>Description</B></TD>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD><B>Job Type</B></TD>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD><B>Start Time</B></TD>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD><B>Current State</B></TD>" + EOL);
        htmlBody.append("    </TR>" + EOL);

        boolean selectAll = false;
        boolean deselectAll = false;

        String submitStr = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
        if (submitStr != null) {
            if (submitStr.equals(Constants.SUBMIT_STRING_SELECT_ALL)) {
                selectAll = true;
            } else if (submitStr.equals(Constants.SUBMIT_STRING_DESELECT_ALL)) {
                deselectAll = true;
            }
        }

        String[] selectedJobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
        if (selectedJobIDs == null) {
            selectedJobIDs = new String[0];
        }

        for (int i = 0; i < jobs.length; i++) {
            if (i % 2 == 0) {
                htmlBody.append("    <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_B + "\">" + EOL);
            } else {
                htmlBody.append("    <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_A + "\">" + EOL);
            }

            String description = (jobs[i].getJobDescription() == null) ? "" : jobs[i].getJobDescription();
            String stateStr;
            switch (jobs[i].getJobState()) {
            case Constants.JOB_STATE_NOT_YET_STARTED:
                stateStr = "Pending";
                break;
            case Constants.JOB_STATE_DISABLED:
                stateStr = "Disabled";
                break;
            case Constants.JOB_STATE_RUNNING:
                stateStr = "Running";
                break;
            case Constants.JOB_STATE_CANCELLED:
            case Constants.JOB_STATE_STOPPED_BY_USER:
                stateStr = "Cancelled";
                break;
            case Constants.JOB_STATE_COMPLETED_SUCCESSFULLY:
            case Constants.JOB_STATE_COMPLETED_WITH_ERRORS:
            case Constants.JOB_STATE_STOPPED_DUE_TO_DURATION:
            case Constants.JOB_STATE_STOPPED_DUE_TO_STOP_TIME:
                stateStr = "Completed";
                break;
            default:
                stateStr = "Stopped";
                break;
            }

            boolean selected = false;
            if (selectAll) {
                selected = true;
            } else if (!deselectAll) {
                for (int j = 0; j < selectedJobIDs.length; j++) {
                    if (selectedJobIDs[j].equals(jobs[i].getJobID())) {
                        selected = true;
                        break;
                    }
                }
            }

            String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                    Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobs[i].getJobID(),
                    Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER, folderName, jobs[i].getJobID());
            htmlBody.append("      <TD><INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_JOB_ID
                    + "\" VALUE=\"" + jobs[i].getJobID() + '"' + (selected ? " CHECKED" : "") + "></TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + link + "</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + description + "</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + jobs[i].getJobName() + "</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + displayDateFormat.format(jobs[i].getStartTime()) + "</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + stateStr + "</TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
        }

        htmlBody.append("  </TABLE>" + EOL);
        htmlBody.append("  <BR><BR>" + EOL);

        ArrayList<String> actions = new ArrayList<String>();
        actions.add(Constants.SUBMIT_STRING_SELECT_ALL);
        actions.add(Constants.SUBMIT_STRING_DESELECT_ALL);
        if (requestInfo.mayScheduleJob) {
            actions.add(Constants.SUBMIT_STRING_CLONE);
        }

        if (requestInfo.mayViewJob && (!disableGraphs)) {
            actions.add(Constants.SUBMIT_STRING_COMPARE);
        }

        if (requestInfo.mayExportJobData) {
            actions.add(Constants.SUBMIT_STRING_EXPORT);
        }

        if (requestInfo.mayManageFolders) {
            actions.add(Constants.SUBMIT_STRING_REMOVE_FROM_VIRTUAL_FOLDER);
        }

        if ((reportGenerators != null) && (reportGenerators.length > 0)) {
            actions.add(Constants.SUBMIT_STRING_GENERATE_REPORT);
        }

        htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
        htmlBody.append("    <TR>" + EOL);

        for (int i = 0; i < actions.size(); i++) {
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT
                    + "\" VALUE=\"" + actions.get(i) + "\">" + EOL);

            if (((i % 5) == 4) && (i < (actions.size() - 1))) {
                htmlBody.append("    </TR>" + EOL);
                htmlBody.append("    <TR>" + EOL);
            }
        }

        htmlBody.append("    </TR>" + EOL);
        htmlBody.append("  </TABLE>" + EOL);

        htmlBody.append("</FORM>" + EOL);
    }

    /**
     * Handles all processing necessary to generate a report of job results.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleGenerateReport(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleGenerateReport()");

        // If the user doesn't have view job permission, then they can't see this
        if (!requestInfo.mayViewJob) {
            logMessage(requestInfo, "No mayViewJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "view job information.");
            return;
        }

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Make sure that at least one report generator has been defined.
        if ((reportGenerators == null) || (reportGenerators.length == 0)) {
            infoMessage.append("ERROR:  No report generators defined<BR>" + EOL);
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Generate Job Data Report</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Job reporting is unavailable because no report "
                    + "generators have been defined in the configuration." + EOL);
            return;
        }

        // Determine which jobs should be included in the report.
        String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
        if (jobIDs == null) {
            jobIDs = new String[0];
        }

        String[] optimizingJobIDs = request.getParameterValues(Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID);
        if (optimizingJobIDs == null) {
            optimizingJobIDs = new String[0];
        }

        if ((jobIDs.length == 0) && (optimizingJobIDs.length == 0)) {
            boolean virtualFolder = false;
            String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
            if ((folderName == null) || (folderName.length() == 0)) {
                folderName = request.getParameter(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER);
                if (folderName != null) {
                    virtualFolder = true;
                }
            }

            try {
                Job[] jobs;
                OptimizingJob[] optimizingJobs;
                if (virtualFolder) {
                    jobs = configDB.getSummaryVirtualJobs(folderName);
                    optimizingJobs = new OptimizingJob[0];
                } else {
                    jobs = configDB.getSummaryJobs(folderName);
                    optimizingJobs = configDB.getSummaryOptimizingJobs(folderName);
                }

                jobIDs = new String[jobs.length];
                for (int i = 0; i < jobs.length; i++) {
                    jobIDs[i] = jobs[i].getJobID();
                }

                optimizingJobIDs = new String[optimizingJobs.length];
                for (int i = 0; i < optimizingJobs.length; i++) {
                    optimizingJobIDs[i] = optimizingJobs[i].getOptimizingJobID();
                }
            } catch (Exception e) {
                slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e));

                infoMessage
                        .append("ERROR:  Unable to retrieve job information -- " + e.getMessage() + "<BR>" + EOL);
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                        + "\">Generate Job Data Report</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("An error occurred while trying to retrieve "
                        + "information about the jobs and/or optimizing jobs " + "in folder " + folderName + '.'
                        + EOL);
                return;
            }

            if ((jobIDs.length == 0) && (optimizingJobIDs.length == 0)) {
                infoMessage.append("ERROR:  No job information found<BR>" + EOL);
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                        + "\">Generate Job Data Report</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("No job or optimizing job data was found in folder " + folderName + '.' + EOL);
                return;
            }
        }

        // Determine whether we know which report generator to use.
        ReportGenerator reportGenerator = null;
        if (reportGenerators.length == 1) {
            reportGenerator = reportGenerators[0].newInstance();
        } else {
            String generatorName = request.getParameter(Constants.SERVLET_PARAM_REPORT_GENERATOR);
            if ((generatorName != null) && (generatorName.length() > 0)) {
                for (int i = 0; i < reportGenerators.length; i++) {
                    if (generatorName.equals(reportGenerators[i].getClass().getName())) {
                        reportGenerator = reportGenerators[i].newInstance();
                        break;
                    }
                }
            }
        }

        if (reportGenerator == null) {
            // We need to provide a form that allows the user to select the report
            // generator.
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Generate Job Data Report</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Please select the report generator to use." + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">");
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_GENERATE_REPORT) + EOL);
            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }
            for (int i = 0; i < optimizingJobIDs.length; i++) {
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID, optimizingJobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <SELECT NAME=\"" + Constants.SERVLET_PARAM_REPORT_GENERATOR + "\">");
            for (int i = 0; i < reportGenerators.length; i++) {
                htmlBody.append("    <OPTION VALUE=\"" + reportGenerators[i].getClass().getName() + "\">"
                        + reportGenerators[i].getReportGeneratorName() + EOL);
            }
            htmlBody.append("  </SELECT>");
            htmlBody.append("  <BR><BR>");
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Continue\">" + EOL);
            htmlBody.append("</FORM>");
            return;
        }

        // Determine whether the user has submitted the form to configure the
        // report parameters.
        Parameter[] params = reportGenerator.newInstance().getReportParameterStubs().getParameters();
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr != null) && (confirmStr.length() > 0)) {
            boolean configValid = true;
            for (int i = 0; i < params.length; i++) {
                String[] values = request
                        .getParameterValues(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX + params[i].getName());
                try {
                    params[i].htmlInputFormToValue(values);
                } catch (InvalidValueException ive) {
                    infoMessage.append("Invalid value for parameter \"" + params[i].getDisplayName() + "\" -- "
                            + ive.getMessage() + "<BR>" + EOL);
                    configValid = false;
                }
            }

            if (configValid) {
                reportGenerator.initializeReporter(new ParameterList(params));

                for (int i = 0; i < jobIDs.length; i++) {
                    try {
                        Job job = configDB.getJob(jobIDs[i]);
                        reportGenerator.addJobReport(job);
                    } catch (Exception e) {
                        // We should probably display an error page here, but since it
                        // should be very rare, just log a message and continue.
                        slamdServer.logMessage(Constants.LOG_LEVEL_JOB_PROCESSING,
                                "Unable to retrieve job " + jobIDs[i] + " to include in generated report");
                    }
                }

                for (int i = 0; i < optimizingJobIDs.length; i++) {
                    try {
                        OptimizingJob optimizingJob = getOptimizingJob(optimizingJobIDs[i]);
                        reportGenerator.addOptimizingJobReport(optimizingJob);
                    } catch (Exception e) {
                        // We should probably display an error page here, but since it
                        // should be very rare, just log a message and continue.
                        slamdServer.logMessage(Constants.LOG_LEVEL_JOB_PROCESSING,
                                "Unable to retrieve optimizing job " + optimizingJobIDs[i]
                                        + " to include in generated report");
                    }
                }

                requestInfo.generateHTML = false;
                reportGenerator.generateReport(requestInfo);
                return;
            }
        }

        // Display the form that allows the user to customize the report
        // that will be generated.
        htmlBody.append(
                "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Generate Job Data Report</SPAN>" + EOL);
        htmlBody.append("<BR><BR>" + EOL);
        htmlBody.append("Please indicate how the report should be generated." + EOL);
        htmlBody.append("<BR><BR>" + EOL);
        htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">");
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_GENERATE_REPORT)
                + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_REPORT_GENERATOR, reportGenerator.getClass().getName())
                + EOL);
        htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_CONFIRMED, "1") + EOL);
        for (int i = 0; i < jobIDs.length; i++) {
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
        }
        for (int i = 0; i < optimizingJobIDs.length; i++) {
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID, optimizingJobIDs[i]) + EOL);
        }

        if (requestInfo.debugHTML) {
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }

        htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
        for (int i = 0; i < params.length; i++) {
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>" + params[i].getDisplayName() + "</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + params[i].getHTMLInputForm(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX)
                    + "</TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
        }

        htmlBody.append("    <TR>" + EOL);
        htmlBody.append("      <TD COLSPAN=\"3\">&nbsp;</TD>" + EOL);
        htmlBody.append("    </TR>" + EOL);
        htmlBody.append("    <TR>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
        htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" " + "VALUE=\"Continue\"></TD>" + EOL);
        htmlBody.append("    </TR>" + EOL);
        htmlBody.append("  </TABLE>" + EOL);
        htmlBody.append("</FORM>");
    }

    /**
     * Handles all processing necessary to perform operations on multiple jobs at
     * the same time.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleMassOperation(RequestInfo requestInfo) {
        HttpServletRequest request = requestInfo.request;

        // Get the job IDs of the jobs on which to perform the operation.
        String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);

        // Determine which mass operation the user wishes to perform.
        String opString = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
        if (opString == null) {
            return;
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_SELECT_ALL)
                || opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DESELECT_ALL)) {
            handleViewJob(requestInfo, request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY), null, null);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_CANCEL)) {
            handleMassCancel(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_CANCEL_AND_DELETE)) {
            handleMassCancelAndDelete(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_CLONE)) {
            handleMassClone(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_COMPARE)) {
            handleMassCompare(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DELETE)) {
            handleMassDelete(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DISABLE)) {
            handleMassDisable(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_ENABLE)) {
            handleMassEnable(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_EXPORT)) {
            handleMassExport(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_CREATE_FOLDER)) {
            handleCreateFolder(requestInfo);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DELETE_FOLDER)) {
            handleDeleteFolder(requestInfo);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_MOVE)) {
            handleMoveJobs(requestInfo);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_ADD_TO_VIRTUAL_FOLDER)) {
            handleAddToVirtualFolder(requestInfo);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_PUBLISH_JOBS)) {
            handleMassPublish(requestInfo, true);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DEPUBLISH_JOBS)) {
            handleMassPublish(requestInfo, false);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_GENERATE_REPORT)) {
            handleGenerateReport(requestInfo);
        }
    }

    /**
     * Handles the work of cancelling multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to cancel.
     */
    static void handleMassCancel(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassCancel()");

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // The user must have permission to cancel jobs to access this section.
        if (!requestInfo.mayCancelJob) {
            logMessage(requestInfo, "No mayCancelJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "cancel jobs.");
            return;
        }

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No jobs specified to cancel.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if the user has provided confirmation.  If so, then cancel the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Cancel Selected Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to cancel the selected " + "jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobIDs[i],
                        jobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_CANCEL) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, category) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            for (int i = 0; i < jobIDs.length; i++) {
                scheduler.cancelJob(jobIDs[i], false);
                infoMessage.append("Requested cancel for job " + jobIDs[i] + ".<BR>" + EOL);
            }

            infoMessage.append("It may take several seconds for all jobs to be " + "cancelled.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        } else {
            infoMessage.append("No jobs were cancelled.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        }
    }

    /**
     * Handles the work of cancelling multiple jobs and removing them from SLAMD
     * entirely.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to cancel.
     */
    static void handleMassCancelAndDelete(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassCancelAndDelete()");

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // The user must have permission to cancel and delete jobs to access this
        // section.
        if (!requestInfo.mayCancelJob) {
            logMessage(requestInfo, "No mayCancelJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "cancel jobs.");
            return;
        } else if (!requestInfo.mayDeleteJob) {
            logMessage(requestInfo, "No mayDeleteJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "delete jobs.");
            return;
        }

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No jobs specified to cancel and delete.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if any of the jobs are no longer running or are associated with
        // optimizing jobs.  If so, then remove them from the list of jobs on which
        // we will operate.
        ArrayList<String> idList = new ArrayList<String>(jobIDs.length);
        for (int i = 0; i < jobIDs.length; i++) {
            Job job = scheduler.getPendingJob(jobIDs[i]);
            if (job == null) {
                infoMessage.append("Not going to cancel and delete job " + jobIDs[i]
                        + " because it is no longer in the pending jobs " + "queue.<BR>" + EOL);
            } else if (job.getOptimizingJobID() != null) {
                infoMessage.append("Not going to cancel and delete job " + jobIDs[i]
                        + " because it is associated with an optimizing " + "job.<BR>" + EOL);
            } else {
                idList.add(jobIDs[i]);
            }
        }

        jobIDs = new String[idList.size()];
        idList.toArray(jobIDs);
        if (jobIDs.length == 0) {
            infoMessage.append("No jobs left to cancel and delete.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if the user has provided confirmation.  If so, then cancel the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                    + "\">Cancel and Delete Selected Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to cancel and delete the " + "selected jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobIDs[i],
                        jobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_CANCEL_AND_DELETE)
                            + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, category) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            for (int i = 0; i < jobIDs.length; i++) {
                scheduler.cancelAndDeleteJob(jobIDs[i]);
                infoMessage.append("Requested cancel and delete for job " + jobIDs[i] + ".<BR>" + EOL);
            }

            handleViewJob(requestInfo, category, null, null);
        } else {
            infoMessage.append("No jobs were cancelled.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        }
    }

    /**
     * Handles the work of cloning multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to clone.
     */
    static void handleMassClone(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassClone()");

        // The user must have permission to schedule jobs to access this section.
        if (!requestInfo.mayScheduleJob) {
            logMessage(requestInfo, "No mayScheduleJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "clone jobs.");
            return;
        }

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No jobs specified to clone.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if the user has provided confirmation.  If so, then clone the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Clone Selected Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to clone the selected " + "jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobIDs[i],
                        jobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_CLONE) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, category) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            try {
                JobFolder[] folders = configDB.getFolders();
                if ((folders != null) && (folders.length > 0)) {
                    String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
                    if (folderName == null) {
                        folderName = "";
                    }

                    htmlBody.append("  Place jobs in folder " + EOL);
                    htmlBody.append("  <SELECT NAME=\"" + Constants.SERVLET_PARAM_JOB_FOLDER + "\">" + EOL);
                    htmlBody.append("    <OPTION VALUE=\"\">Unclassified" + EOL);
                    for (int i = 0; i < folders.length; i++) {
                        htmlBody.append("    <OPTION VALUE=\"" + folders[i].getFolderName() + '"');
                        if (folderName.equals(folders[i].getFolderName())) {
                            htmlBody.append(" SELECTED");
                        }

                        htmlBody.append("> " + folders[i].getFolderName() + EOL);
                    }
                    htmlBody.append("  </SELECT>" + EOL);
                    htmlBody.append("  <BR>" + EOL);
                } else {
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, "") + EOL);
                }
            } catch (DatabaseException de) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, "") + EOL);
            }

            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_JOB_MAKE_INTERDEPENDENT
                    + "\">" + EOL);
            htmlBody.append("  Make Jobs Interdependent" + EOL);
            htmlBody.append("  <BR>" + EOL);

            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_JOB_DISABLED
                    + "\" CHECKED>" + EOL);
            htmlBody.append("  Make Jobs Disabled" + EOL);
            htmlBody.append("  <BR>" + EOL);

            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            boolean makeInterDependent = false;
            boolean makeDisabled = false;
            String folderName = null;

            String dependentID = null;
            String dependStr = request.getParameter(Constants.SERVLET_PARAM_JOB_MAKE_INTERDEPENDENT);
            if (dependStr != null) {
                makeInterDependent = (dependStr.equalsIgnoreCase("true") || dependStr.equalsIgnoreCase("yes")
                        || dependStr.equalsIgnoreCase("on") || dependStr.equalsIgnoreCase("1"));
            }

            String disabledStr = request.getParameter(Constants.SERVLET_PARAM_JOB_DISABLED);
            if (disabledStr != null) {
                makeDisabled = (disabledStr.equalsIgnoreCase("true") || disabledStr.equalsIgnoreCase("yes")
                        || disabledStr.equalsIgnoreCase("on") || disabledStr.equalsIgnoreCase("1"));
            }

            folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
            if ((folderName == null) || (folderName.length() == 0)) {
                folderName = null;
            }

            for (int i = 0; i < jobIDs.length; i++) {
                Job j = null;
                try {
                    j = configDB.getJob(jobIDs[i]);
                    if (j == null) {
                        infoMessage.append("Unable to clone job " + jobIDs[i] + ":  Job not found.<BR>" + EOL);
                        continue;
                    }
                } catch (Exception e) {
                    infoMessage.append("Unable to clone job " + jobIDs[i] + ":  " + e + "<BR>" + EOL);
                    continue;
                }

                try {
                    Job newJob = new Job(slamdServer, j.getJobClassName(), j.getNumberOfClients(),
                            j.getThreadsPerClient(), j.getThreadStartupDelay(), new Date(), null, j.getDuration(),
                            j.getCollectionInterval(), j.getParameterList(), j.displayInReadOnlyMode());

                    if (makeDisabled) {
                        newJob.setJobState(Constants.JOB_STATE_DISABLED);
                    }

                    newJob.setJobDescription(j.getJobDescription());
                    newJob.setWaitForClients(j.waitForClients());
                    newJob.setRequestedClients(j.getRequestedClients());
                    newJob.setResourceMonitorClients(j.getResourceMonitorClients());
                    newJob.setJobComments(j.getJobComments());

                    if (dependentID != null) {
                        newJob.setDependencies(new String[] { dependentID });
                    }

                    // Normally, this wouldn't be required, but we'll do it anyway because
                    // some jobs can do sneaky things in the validateJobInfo() method.
                    try {
                        JobClass jobClass = newJob.getJobClass();
                        jobClass.validateJobInfo(newJob.getNumberOfClients(), newJob.getThreadsPerClient(),
                                newJob.getThreadStartupDelay(), newJob.getStartTime(), newJob.getStopTime(),
                                newJob.getDuration(), newJob.getCollectionInterval(), newJob.getParameterList());
                    } catch (InvalidValueException ive) {
                        infoMessage.append("WARNING:  validateJobInfo failed when " + "cloning job " + j.getJobID()
                                + ":  " + ive.getMessage() + "<BR>" + EOL);
                    }

                    String newJobID = scheduler.scheduleJob(newJob, folderName);
                    infoMessage.append("Successfully scheduled job " + newJobID + " for execution.<BR>" + EOL);

                    if (makeInterDependent) {
                        dependentID = newJobID;
                    }
                } catch (Exception e) {
                    infoMessage.append("Unable to clone job " + jobIDs[i] + ":  " + e + "<BR>" + EOL);
                }
            }

            if (makeDisabled) {
                infoMessage.append("Note that all cloned jobs are currently disabled "
                        + "so that they may be edited as necessary.<BR>" + EOL);
            }
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_PENDING, null, null);
        } else {
            infoMessage.append("No jobs were cloned.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        }
    }

    /**
     * Handles the work of comparing multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to compare.
     */
    static void handleMassCompare(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassCompare()");

        // The user must have permission to view jobs to access this section.
        if (!requestInfo.mayViewJob) {
            logMessage(requestInfo, "No mayViewJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "view job information.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least two job IDs were specified.  If not, then print
        // an appropriate error message and go back to viewing the completed jobs.
        if ((jobIDs == null) || (jobIDs.length < 2)) {
            infoMessage
                    .append("You must specify at least two jobs of the same " + "type to be compared.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // Retrieve all of the jobs and make sure that they are all of the same
        // type.
        ArrayList<Job> jobList = new ArrayList<Job>();
        String jobClass = null;
        for (int i = 0; i < jobIDs.length; i++) {
            Job job = null;
            try {
                job = configDB.getJob(jobIDs[i]);
                if (job == null) {
                    infoMessage.append("Unable to retrieve job " + jobIDs[i] + " -- job not found.<BR>" + EOL);
                } else {
                    // Make sure that the job is of the same type as all other jobs in the
                    // selection.
                    if (jobClass == null) {
                        jobClass = job.getJobClassName();
                    } else {
                        if (!jobClass.equals(job.getJobClassName())) {
                            infoMessage.append(
                                    "Skipping job " + jobIDs[i] + " because it is not of the same type as the "
                                            + " first job selected (" + jobClass + ").<BR>" + EOL);
                            continue;
                        }
                    }

                    // Make sure that the job has statistics available.
                    if (!job.hasStats()) {
                        infoMessage.append("Skipping job " + jobIDs[i] + " because it does not have any statistics "
                                + "available.<BR>" + EOL);
                        continue;
                    }

                    jobList.add(job);
                }
            } catch (Exception e) {
                infoMessage.append("Unable to retrieve job " + jobIDs[i] + " -- " + e + "<BR>" + EOL);
            }
        }

        // Make sure that at least two jobs were placed in the job list.
        if (jobList.size() < 2) {
            infoMessage.append("Unable to perform the requested comparison because "
                    + "there were not at least two jobs meeting the " + "necessary criteria.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // Sort the job information based on the actual start times for the jobs.
        // Since there should not be that many jobs to compare, and since it is
        // likely that they will already be sorted anyway, then a selection sort
        // should be the fastest and simplest way to do it.
        long[] startTimes = new long[jobList.size()];
        Job[] jobs = new Job[jobList.size()];
        for (int i = 0; i < jobs.length; i++) {
            jobs[i] = jobList.get(i);
            startTimes[i] = jobs[i].getActualStartTime().getTime();
        }
        for (int i = 0; i < jobs.length; i++) {
            int slot = -1;
            long minStartTime = startTimes[i];

            for (int j = i + 1; j < jobs.length; j++) {
                if (startTimes[j] < minStartTime) {
                    slot = j;
                    minStartTime = startTimes[j];
                }
            }

            if (slot > 0) {
                Job tempJob = jobs[slot];
                long tempStartTime = startTimes[slot];
                jobs[slot] = jobs[i];
                startTimes[slot] = startTimes[i];
                jobs[i] = tempJob;
                startTimes[i] = tempStartTime;
            }
        }

        // Get the names of the stat trackers that will be available for comparison.
        // For now, this will only be the stat trackers available in the first job
        // in the list of selected jobs.  Although it is possible that the different
        // jobs selected will have different sets of statistics, that introduces too
        // much complexity into this code for the current version.
        String[] trackerNames = jobs[0].getStatTrackerNames();

        // Determine how the comparison should be done.  It can either be a trend
        // comparison (where each job counts as one data point) or a side-by-side
        // comparison (where the jobs are compared across their entire durations).
        String confirmedStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);

        if ((confirmedStr != null) && confirmedStr.equals(Constants.CONFIG_VALUE_FALSE)) {
            String trackerName = request.getParameter(Constants.SERVLET_PARAM_STAT_TRACKER);
            StatTracker infoTracker = jobs[0].getStatTrackers(trackerName)[0];

            int graphWidth = defaultGraphWidth;
            try {
                graphWidth = Integer.parseInt(request.getParameter(Constants.SERVLET_PARAM_GRAPH_WIDTH));
            } catch (Exception e) {
            }

            int graphHeight = defaultGraphHeight;
            try {
                graphHeight = Integer.parseInt(request.getParameter(Constants.SERVLET_PARAM_GRAPH_HEIGHT));
            } catch (Exception e) {
            }

            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Compare " + trackerName
                    + " for Multiple \"" + jobs[0].getJobName() + "\" Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_COMPARE) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_CONFIRMED, Constants.CONFIG_VALUE_FALSE) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_USE_REQUEST_PARAMS, Constants.CONFIG_VALUE_TRUE)
                            + EOL);

            String hideStr = (graphInNewWindow ? Constants.CONFIG_VALUE_TRUE : Constants.CONFIG_VALUE_FALSE);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HIDE_SIDEBAR, hideStr) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            for (int i = 0; i < jobs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobs[i].getJobID()) + EOL);
            }

            htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);

            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Graph Width</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_GRAPH_WIDTH
                    + "\" VALUE=\"" + graphWidth + "\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);

            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Graph Height</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_GRAPH_HEIGHT
                    + "\" VALUE=\"" + graphHeight + "\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);

            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Statistic to Compare</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <SELECT NAME=\"" + Constants.SERVLET_PARAM_STAT_TRACKER + "\">" + EOL);
            for (int i = 0; i < trackerNames.length; i++) {
                if (trackerName.equalsIgnoreCase(trackerNames[i])) {
                    htmlBody.append("          <OPTION SELECTED VALUE=\"" + trackerNames[i] + "\">"
                            + trackerNames[i] + EOL);
                } else {
                    htmlBody.append("          <OPTION VALUE=\"" + trackerNames[i] + "\">" + trackerNames[i] + EOL);
                }
            }
            htmlBody.append("        </SELECT>" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);

            Parameter[] params = infoTracker.getGraphParameterStubs(jobs).clone().getParameters();
            String useRequestValuesStr = request.getParameter(Constants.SERVLET_PARAM_USE_REQUEST_PARAMS);
            boolean useRequestValues = ((useRequestValuesStr != null)
                    && (useRequestValuesStr.equals(Constants.CONFIG_VALUE_TRUE)));
            for (int i = 0; i < params.length; i++) {
                if (useRequestValues) {
                    try {
                        String[] values = request
                                .getParameterValues(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX + params[i].getName());
                        params[i].htmlInputFormToValue(values);
                    } catch (InvalidValueException ive) {
                    }
                }

                htmlBody.append("    <TR>" + EOL);
                htmlBody.append("      <TD>" + params[i].getDisplayName() + "</TD>" + EOL);
                htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("      <TD>" + params[i].getHTMLInputForm(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX)
                        + "</TD>" + EOL);
                htmlBody.append("    </TR>" + EOL);
            }

            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD COLSPAN=\"3\">&nbsp;</TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);

            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD COLSPAN=\"3\"><INPUT TYPE=\"SUBMIT\" " + "VALUE=\"Compare\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);

            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);

            // Generate the image tag.  Replace any spaces with the '+' sign.
            try {
                htmlBody.append("<BR><BR>" + EOL);
                String imageURI = servletBaseURI + '?' + Constants.SERVLET_PARAM_SECTION + '='
                        + Constants.SERVLET_SECTION_JOB + '&' + Constants.SERVLET_PARAM_SUBSECTION + '='
                        + Constants.SERVLET_SECTION_JOB_GRAPH + '&' + Constants.SERVLET_PARAM_GRAPH_WIDTH + '='
                        + graphWidth + '&' + Constants.SERVLET_PARAM_GRAPH_HEIGHT + '=' + graphHeight + '&'
                        + Constants.SERVLET_PARAM_STAT_TRACKER + '=' + URLEncoder.encode(trackerName, "UTF-8");
                for (int i = 0; i < jobIDs.length; i++) {
                    imageURI += '&' + Constants.SERVLET_PARAM_JOB_ID + '=' + jobIDs[i];
                }
                for (int i = 0; i < params.length; i++) {
                    imageURI += '&' + Constants.SERVLET_PARAM_JOB_PARAM_PREFIX + params[i].getName() + '='
                            + params[i].getValueString();
                }

                htmlBody.append(
                        "<IMG SRC=\"" + imageURI.replace(' ', '+') + "\" WIDTH=\"" + graphWidth + "\" HEIGHT=\""
                                + graphHeight + "\" ALT=\"Graph of Results for Job " + "Comparison\">" + EOL);
            } catch (Exception e) {
                slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e));
            }
        } else {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Compare Multiple \""
                    + jobs[0].getJobName() + "\" Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + '"' + blankTarget + '>' + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_COMPARE) + EOL);

            // Why is this set to "false"?  It's a subtle difference, but it's used
            // in the previous section when assigning values to the parameters for the
            // stat tracker.  If the value is set to "true", then the values of those
            // parameters will be set from the request parameters.  If it's set to
            // something else, then the default values will be used.
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_CONFIRMED, Constants.CONFIG_VALUE_FALSE) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_USE_REQUEST_PARAMS, Constants.CONFIG_VALUE_FALSE)
                            + EOL);

            String hideStr = (graphInNewWindow ? Constants.CONFIG_VALUE_TRUE : Constants.CONFIG_VALUE_FALSE);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HIDE_SIDEBAR, hideStr) + EOL);

            for (int i = 0; i < jobs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobs[i].getJobID()) + EOL);
            }
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);

            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Statistic to Graph</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <SELECT NAME=\"" + Constants.SERVLET_PARAM_STAT_TRACKER + "\">" + EOL);
            for (int i = 0; i < trackerNames.length; i++) {
                htmlBody.append("          <OPTION VALUE=\"" + trackerNames[i] + "\">" + trackerNames[i] + EOL);
            }
            htmlBody.append("        </SELECT>" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" VALUE=\"Graph\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);

            htmlBody.append("</FORM>" + EOL);
            htmlBody.append("<BR>" + EOL);

            boolean oneFound = false;
            boolean twoFound = false;
            for (int i = 0; i < trackerNames.length; i++) {
                StatTracker[] trackers = jobs[0].getStatTrackers(trackerNames[i]);
                if ((trackers != null) && (trackers.length > 0) && trackers[0].isSearchable()) {
                    if (oneFound) {
                        twoFound = true;
                        break;
                    } else {
                        oneFound = true;
                    }
                }
            }

            if (twoFound) {
                htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + '"' + blankTarget + '>' + EOL);
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                        Constants.SERVLET_SECTION_JOB_VIEW_OVERLAY) + EOL);
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HIDE_SIDEBAR, hideStr) + EOL);

                for (int i = 0; i < jobs.length; i++) {
                    htmlBody.append(
                            "  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobs[i].getJobID()) + EOL);
                }
                if (requestInfo.debugHTML) {
                    htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                }

                htmlBody.append("  <INPUT TYPE=\"SUBMIT\" " + "VALUE=\"Graph Overlaid Statistics\">" + EOL);
                htmlBody.append("</FORM>" + EOL);
                htmlBody.append("<BR>" + EOL);
            }

            for (int i = 0; i < trackerNames.length; i++) {
                htmlBody.append("<B>" + trackerNames[i] + "</B><BR>" + EOL);
                htmlBody.append("<TABLE BORDER=\"1\">" + EOL);

                boolean labelsDisplayed = false;
                for (int j = 0; j < jobs.length; j++) {
                    StatTracker[] trackers = jobs[j].getStatTrackers(trackerNames[i]);
                    if ((trackers != null) && (trackers.length > 0)) {
                        StatTracker tracker = trackers[0].newInstance();
                        tracker.aggregate(trackers);

                        if (!labelsDisplayed) {
                            String[] labels = tracker.getSummaryLabels();
                            htmlBody.append("  <TR>" + EOL);
                            htmlBody.append("    <TD><B>Job ID</B></TD>" + EOL);
                            htmlBody.append("    <TD><B>Job Description</B></TD>" + EOL);

                            for (int k = 0; k < labels.length; k++) {
                                htmlBody.append("    <TD><B>" + labels[k] + "</B></TD>" + EOL);
                            }

                            htmlBody.append("  </TR>" + EOL);
                            labelsDisplayed = true;
                        }

                        htmlBody.append("  <TR>" + EOL);

                        String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                                Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID,
                                jobs[j].getJobID(), jobs[j].getJobID());
                        htmlBody.append("    <TD>" + link + "</TD>" + EOL);
                        htmlBody.append("    <TD>" + jobs[j].getJobDescription() + "</TD>" + EOL);

                        String[] data = tracker.getSummaryData();
                        for (int k = 0; k < data.length; k++) {
                            htmlBody.append("    <TD>" + data[k] + "</TD>" + EOL);
                        }

                        htmlBody.append("  </TR>" + EOL);
                    }
                }

                htmlBody.append("</TABLE>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
            }
        }
    }

    /**
     * Handles the work of deleting multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to delete.
     */
    static void handleMassDelete(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassDelete()");

        // The user must have permission to delete jobs to access this section.
        if (!requestInfo.mayDeleteJob) {
            logMessage(requestInfo, "No mayDeleteJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "delete jobs.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder infoMessage = requestInfo.infoMessage;
        StringBuilder htmlBody = requestInfo.htmlBody;

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No jobs specified to delete.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if the user has provided confirmation.  If so, then delete the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete Selected Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to delete the selected " + "jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobIDs[i],
                        jobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_DELETE) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, category) + EOL);

            String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
            if ((folderName != null) && (folderName.length() > 0)) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);
            }

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            for (int i = 0; i < jobIDs.length; i++) {
                Job job = null;
                try {
                    job = configDB.getJob(jobIDs[i]);
                } catch (Exception e) {
                }
                if ((job != null) && (job.getOptimizingJobID() != null)) {
                    OptimizingJob optimizingJob = null;
                    try {
                        optimizingJob = getOptimizingJob(job.getOptimizingJobID());
                        if (optimizingJob != null) {
                            infoMessage.append(
                                    "Not removing job " + jobIDs[i] + " because it is associated with optimizing "
                                            + "job " + job.getOptimizingJobID() + "<BR>" + EOL);
                            continue;
                        }
                    } catch (Exception e) {
                    }
                }

                try {
                    configDB.removeJob(jobIDs[i]);
                    infoMessage.append("Deleted job " + jobIDs[i] + ".<BR>" + EOL);
                } catch (DatabaseException de) {
                    infoMessage.append("Unable to delete job " + jobIDs[i] + ":  " + de + "<BR>" + EOL);
                }
            }

            handleViewJob(requestInfo, category, null, null);
        } else {
            infoMessage.append("No jobs were deleted.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        }
    }

    /**
     * Handles the work of disabling multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to disable.
     */
    static void handleMassDisable(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassDisable()");

        // The user must have permission to schedule jobs to access this section.
        if (!requestInfo.mayScheduleJob) {
            logMessage(requestInfo, "No mayScheduleJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "disable jobs.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No jobs specified to disable.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if the user has provided confirmation.  If so, then disable the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Disable Selected Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to disable the selected " + "jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobIDs[i],
                        jobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_DISABLE) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, category) + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            for (int i = 0; i < jobIDs.length; i++) {
                try {
                    scheduler.disableJob(jobIDs[i]);
                    infoMessage.append("Disabled job " + jobIDs[i] + ".<BR>" + EOL);
                } catch (SLAMDServerException sse) {
                    infoMessage.append("Unable to disable job " + jobIDs[i] + ":  " + sse + "<BR>" + EOL);
                }
            }

            handleViewJob(requestInfo, category, null, null);
        } else {
            infoMessage.append("No jobs were disabled.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        }
    }

    /**
     * Handles the work of enabling multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to enable.
     */
    static void handleMassEnable(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassEnable()");

        // The user must have permission to schedule jobs to access this section.
        if (!requestInfo.mayScheduleJob) {
            logMessage(requestInfo, "No mayScheduleJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "enable jobs.");
            return;
        }

        // Get the important state information for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No jobs specified to enable.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // See if the user has provided confirmation.  If so, then enable the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Enable Selected Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to enable the selected " + "jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_GENERIC, Constants.SERVLET_PARAM_JOB_ID, jobIDs[i],
                        jobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_ENABLE) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIEW_CATEGORY, category) + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            for (int i = 0; i < jobIDs.length; i++) {
                try {
                    scheduler.enableJob(jobIDs[i]);
                    infoMessage.append("Enabled job " + jobIDs[i] + ".<BR>" + EOL);
                } catch (SLAMDServerException sse) {
                    infoMessage.append("Unable to enable job " + jobIDs[i] + ":  " + sse + "<BR>" + EOL);
                }
            }

            handleViewJob(requestInfo, category, null, null);
        } else {
            infoMessage.append("No jobs were enabled.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
        }
    }

    /**
     * Handles the work of exporting information about multiple jobs.
     *
     * @param  requestInfo  The state information for this request.
     * @param  jobIDs       The job IDs of the jobs to export.
     */
    static void handleMassExport(RequestInfo requestInfo, String[] jobIDs) {
        logMessage(requestInfo, "In handleMassExport()");

        // The user must have permission to export jobs to access this section.
        if (!requestInfo.mayExportJobData) {
            logMessage(requestInfo, "No mayViewJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "export job information.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String category = request.getParameter(Constants.SERVLET_PARAM_VIEW_CATEGORY);

        // Make sure that at least one job ID was specified.  If not, then print
        // an appropriate error message and go back to viewing the completed jobs.
        if ((jobIDs == null) || (jobIDs.length < 1)) {
            infoMessage.append("You must specify at least one job to export.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        // Retrieve all of the jobs and see if they are all of the same type.  If
        // so, then we'll be able to be pretty specific about the kinds of data
        // that can be exported.  Otherwise, it will only be possible to choose
        // what gets exported in a more generic manner.
        ArrayList<ArrayList<Job>> jobTypeList = new ArrayList<ArrayList<Job>>();
        ArrayList<String> jobIDList = new ArrayList<String>();
        for (int i = 0; i < jobIDs.length; i++) {
            Job job = null;
            try {
                job = configDB.getJob(jobIDs[i]);
                if (job == null) {
                    infoMessage.append("Unable to retrieve job " + jobIDs[i] + " -- job not found.<BR>" + EOL);
                } else {
                    // Make sure that the job has statistics available.
                    if (!job.hasStats()) {
                        infoMessage.append("Skipping job " + jobIDs[i] + " because it does not have any statistics "
                                + "available.<BR>" + EOL);
                        continue;
                    }

                    // Get the job type for this job and see if it is the same as any
                    // of the other jobs that we have already seen.
                    jobIDList.add(jobIDs[i]);
                    boolean categorized = false;
                    for (int j = 0; j < jobTypeList.size(); j++) {
                        ArrayList<Job> jobList = jobTypeList.get(j);
                        Job job2 = jobList.get(0);
                        if (job2.getJobClassName().equals(job.getJobClassName())) {
                            jobList.add(job);
                            categorized = true;
                            break;
                        }
                    }
                    if (!categorized) {
                        ArrayList<Job> jobList = new ArrayList<Job>();
                        jobList.add(job);
                        jobTypeList.add(jobList);
                    }
                }
            } catch (Exception e) {
                infoMessage.append("Unable to retrieve job " + jobIDs[i] + " -- " + e + "<BR>" + EOL);
            }
        }

        // Make sure that at least one job was placed in the job list.
        if (jobTypeList.isEmpty()) {
            infoMessage.append("Unable to perform the requested comparison because "
                    + "there was not at least one job meeting the " + "necessary criteria.<BR>" + EOL);
            handleViewJob(requestInfo, category, null, null);
            return;
        }

        jobIDs = new String[jobIDList.size()];
        jobIDList.toArray(jobIDs);

        // Sort the job information based on the actual start times for the jobs.
        // Since there should not be that many jobs to compare, and since it is
        // likely that they will already be sorted anyway, then a selection sort
        // should be the fastest and simplest way to do it.
        for (int i = 0; i < jobTypeList.size(); i++) {
            ArrayList<Job> jobList = jobTypeList.get(i);
            long[] startTimes = new long[jobList.size()];
            Job[] jobs = new Job[jobList.size()];
            for (int j = 0; j < jobs.length; j++) {
                jobs[j] = jobList.get(j);
                startTimes[j] = jobs[j].getActualStartTime().getTime();
            }
            for (int j = 0; j < jobs.length; j++) {
                int slot = -1;
                long minStartTime = startTimes[j];

                for (int k = j + 1; k < jobs.length; k++) {
                    if (startTimes[k] < minStartTime) {
                        slot = k;
                        minStartTime = startTimes[k];
                    }
                }

                if (slot > 0) {
                    Job tempJob = jobs[slot];
                    long tempStartTime = startTimes[slot];

                    jobs[slot] = jobs[j];
                    startTimes[slot] = startTimes[j];
                    jobs[j] = tempJob;
                    startTimes[j] = tempStartTime;
                }
            }

            jobList.clear();
            for (int j = 0; j < jobs.length; j++) {
                jobList.add(jobs[j]);
            }
        }

        // Determine whether the user has chosen the kinds of information to be
        // exported.
        String confirmedStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);

        if ((confirmedStr != null) && confirmedStr.equals(Constants.CONFIG_VALUE_TRUE)) {
            // Determine the kinds of information that will be included in the output.
            String value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_JOB_ID);
            boolean includeJobID = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_INCLUDE_LABELS);
            boolean includeLabels = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_DESCRIPTION);
            boolean includeDescription = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_START_TIME);
            boolean includeStartTime = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_STOP_TIME);
            boolean includeStopTime = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_DURATION);
            boolean includeDuration = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_CLIENTS);
            boolean includeClients = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_THREADS);
            boolean includeThreads = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_INTERVAL);
            boolean includeInterval = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_PARAMETERS);
            boolean includeAllParams = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_STATISTICS);
            boolean includeAllStats = (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                    || value.equalsIgnoreCase("off")));

            ArrayList<Parameter> includeParameters = new ArrayList<Parameter>();
            ArrayList<StatTracker> includeStats = new ArrayList<StatTracker>();
            if (jobTypeList.size() == 1) {
                ArrayList<Job> jobList = jobTypeList.get(0);
                Job job = jobList.get(0);
                Parameter[] stubs = job.getParameterStubs().getParameters();
                for (int i = 0; i < stubs.length; i++) {
                    value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_PARAM_PREFIX + stubs[i].getName());
                    if (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                            || value.equalsIgnoreCase("off"))) {
                        includeParameters.add(stubs[i]);
                    }
                }

                String[] trackerNames = job.getStatTrackerNames();
                for (int i = 0; i < trackerNames.length; i++) {
                    value = request.getParameter(Constants.SERVLET_PARAM_EXPORT_STAT_PREFIX + trackerNames[i]);
                    if (!((value == null) || value.equals("0") || value.equalsIgnoreCase("false")
                            || value.equalsIgnoreCase("off"))) {
                        // There must be at least one job with this stat tracker in order to
                        // export this information.
                        for (int j = 0; j < jobList.size(); j++) {
                            StatTracker[] trackers = jobList.get(j).getStatTrackers(trackerNames[i]);
                            if ((trackers != null) && (trackers.length > 0)) {
                                includeStats.add(trackers[0]);
                                break;
                            }
                        }
                    }
                }
            }

            // Get the writer to use to send data to the client.
            PrintWriter writer = null;
            try {
                writer = requestInfo.response.getWriter();
            } catch (IOException ioe) {
                infoMessage.append("ERROR:  Unable to write the data -- " + ioe + "<BR>" + EOL);
                htmlBody.append(
                        "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Error Saving Data</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("The attempt to save the data failed." + EOL);
                htmlBody.append("See the error message above for additional " + "information");
                return;
            }

            // Indicate that the output from this will not be HTML.
            requestInfo.generateHTML = false;
            requestInfo.response.setContentType("application/x-slamd-job-export");
            requestInfo.response.addHeader("Content-Disposition", "filename=\"slamd_job_export_data.txt\"");

            // Iterate through all the different kinds of jobs to include in the
            // export.
            for (int i = 0; i < jobTypeList.size(); i++) {
                // Get the job information as an array.
                ArrayList<Job> jobList = jobTypeList.get(i);
                Job[] jobs = new Job[jobList.size()];
                jobList.toArray(jobs);

                // If there are multiple types of jobs to retrieve, then get the types
                // of parameters to include for this particular job type.
                if (includeAllParams && (jobTypeList.size() > 1)) {
                    includeParameters.clear();
                    Parameter[] stubs = jobs[0].getParameterStubs().getParameters();
                    for (int j = 0; j < stubs.length; j++) {
                        if (!(stubs[j] instanceof PlaceholderParameter)) {
                            includeParameters.add(stubs[j]);
                        }
                    }
                }

                // If there are multiple types of jobs to retrieve, then get the types
                // of statistics to include for this particular job type.
                if (includeAllStats && (jobTypeList.size() > 1)) {
                    includeStats.clear();
                    String[] trackerNames = jobs[0].getStatTrackerNames();
                    for (int j = 0; j < trackerNames.length; j++) {
                        for (int k = 0; k < jobs.length; k++) {
                            StatTracker[] trackers = jobs[k].getStatTrackers(trackerNames[j]);
                            if ((trackers != null) && (trackers.length > 0)) {
                                includeStats.add(trackers[0]);
                                break;
                            }
                        }
                    }
                }

                // If labels are to be included in the export, then send them out.
                if (includeLabels) {
                    writer.println(jobs[0].getJobName() + " Job Data");

                    if (includeJobID) {
                        writer.print("Job ID\t");
                    }

                    if (includeDescription) {
                        writer.print("Description\t");
                    }

                    if (includeStartTime) {
                        writer.print("Start Time\t");
                    }

                    if (includeStopTime) {
                        writer.print("Stop Time\t");
                    }

                    if (includeDuration) {
                        writer.print("Duration\t");
                    }

                    if (includeClients) {
                        writer.print("Number of Clients\t");
                    }

                    if (includeThreads) {
                        writer.print("Threads per Client\t");
                    }

                    if (includeInterval) {
                        writer.print("Collection Interval\t");
                    }

                    for (int j = 0; j < includeParameters.size(); j++) {
                        Parameter p = includeParameters.get(i);
                        writer.print(p.getDisplayName() + '\t');
                    }

                    for (int j = 0; j < includeStats.size(); j++) {
                        StatTracker tracker = includeStats.get(j);
                        String[] trackerLabels = tracker.getSummaryLabels();
                        for (int k = 0; k < trackerLabels.length; k++) {
                            writer.print(trackerLabels[k] + '\t');
                        }
                    }

                    writer.println();
                }

                // Write out the requested information for each job.
                for (int j = 0; j < jobs.length; j++) {
                    if (includeJobID) {
                        writer.print(jobs[j].getJobID() + '\t');
                    }

                    if (includeDescription) {
                        writer.print(jobs[j].getJobDescription() + '\t');
                    }

                    if (includeStartTime) {
                        String formattedTime = displayDateFormat.format(jobs[j].getActualStartTime());
                        writer.print(formattedTime + '\t');
                    }

                    if (includeStopTime) {
                        String formattedTime = displayDateFormat.format(jobs[j].getActualStopTime());
                        writer.print(formattedTime + '\t');
                    }

                    if (includeDuration) {
                        writer.print(jobs[j].getActualDuration() + "\t");
                    }

                    if (includeClients) {
                        writer.print(jobs[j].getNumberOfClients() + "\t");
                    }

                    if (includeThreads) {
                        writer.print(jobs[j].getThreadsPerClient() + "\t");
                    }

                    if (includeInterval) {
                        writer.print(jobs[j].getCollectionInterval() + "\t");
                    }

                    for (int k = 0; k < includeParameters.size(); k++) {
                        Parameter p = includeParameters.get(k);
                        Parameter q = jobs[i].getParameterList().getParameter(p.getName());
                        if (q == null) {
                            writer.print("\t");
                        } else {
                            writer.print(q.getValueString() + '\t');
                        }
                    }

                    for (int k = 0; k < includeStats.size(); k++) {
                        StatTracker tracker = includeStats.get(k);

                        StatTracker[] trackers = jobs[j].getStatTrackers(tracker.getDisplayName());
                        if ((trackers == null) || (trackers.length == 0)) {
                            for (int l = 0; l < tracker.getSummaryLabels().length; l++) {
                                writer.print("\t");
                            }
                        } else {
                            try {
                                StatTracker t = trackers[0].newInstance();
                                t.aggregate(trackers);
                                String[] values = t.getSummaryData();
                                for (int l = 0; l < values.length; l++) {
                                    writer.print(values[l] + '\t');
                                }
                            } catch (Exception e) {
                                for (int l = 0; l < tracker.getSummaryLabels().length; l++) {
                                    writer.print("\t");
                                }
                            }
                        }
                    }

                    writer.println();
                }

                writer.println();
            }
        } else {
            if (jobTypeList.size() == 1) {
                ArrayList<Job> jobList = jobTypeList.get(0);
                Job job = jobList.get(0);

                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Export Multiple \""
                        + job.getJobName() + "\" Jobs</SPAN>" + EOL);
            } else {
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                        + "\">Export Multiple SLAMD Jobs</SPAN>" + EOL);
            }

            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append(
                    "Please select the information that should be included " + "in the job data export." + EOL);

            if (jobTypeList.size() > 1) {
                htmlBody.append("Note that because multiple job types were selected, "
                        + "it is not possible to choose the individual "
                        + "parameter and statistic types that can be exported." + EOL);
                htmlBody.append("Therefore, it is only possible to indicate whether "
                        + "to include all or no parameter information, and all " + "or no statistical information."
                        + EOL);
            }

            htmlBody.append("<BR><BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\" CLASS=\""
                    + Constants.STYLE_MAIN_FORM + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_EXPORT) + EOL);

            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_CONFIRMED, Constants.CONFIG_VALUE_TRUE) + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <B>Job Schedule Information to Export</B><BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_JOB_ID
                    + "\" CHECKED>Job ID<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_DESCRIPTION
                    + "\" CHECKED>Job Description<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_START_TIME
                    + "\">Job Start Time<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_STOP_TIME
                    + "\">Job Stop Time<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_DURATION
                    + "\">Job Duration<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_CLIENTS
                    + "\">Number of Clients<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_THREADS
                    + "\">Number of Threads per Client<BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_INTERVAL
                    + "\">Statistics Collection Interval<BR>" + EOL);

            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <B>Job Parameter Information to Export</B><BR>" + EOL);
            if (jobTypeList.size() == 1) {
                ArrayList<Job> jobList = jobTypeList.get(0);
                Job job = jobList.get(0);
                Parameter[] stubs = job.getParameterStubs().getParameters();
                for (int i = 0; i < stubs.length; i++) {
                    if (!(stubs[i] instanceof PlaceholderParameter)) {
                        htmlBody.append(
                                "  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_PARAM_PREFIX
                                        + stubs[i].getName() + "\">" + stubs[i].getDisplayName() + "<BR>" + EOL);
                    }
                }
            } else {
                htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_PARAMETERS
                        + "\">Export all parameters<BR>" + EOL);
            }

            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <B>Statistical Information to Export</B><BR>" + EOL);
            if (jobTypeList.size() == 1) {
                ArrayList<Job> jobList = jobTypeList.get(0);
                Job job = jobList.get(0);
                String[] trackerNames = job.getStatTrackerNames();
                for (int i = 0; i < trackerNames.length; i++) {
                    htmlBody.append(
                            "  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_STAT_PREFIX
                                    + trackerNames[i] + "\" CHECKED>" + trackerNames[i] + "<BR>" + EOL);
                }
            } else {
                htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_STATISTICS
                        + "\" CHECKED>Export all statistics<BR>" + EOL);
            }

            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_INCLUDE_LABELS
                    + "\" CHECKED>Include Labels in Exported Data<BR>" + EOL);
            htmlBody.append("  <BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Export Data\"><BR>" + EOL);

            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles the work of publishing or de-publishing one or more jobs for
     * display in restricted read-only mode.
     *
     * @param  requestInfo            The state information for this request.
     * @param  displayInReadOnlyMode  Indicates whether the specified jobs should
     *                                be published or de-published.
     */
    static void handleMassPublish(RequestInfo requestInfo, boolean displayInReadOnlyMode) {
        logMessage(requestInfo, "In handleMassPublish()");

        // The user must have permission to manage folders to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "manage job folders.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Get the job IDs of the jobs to publish or de-publish.
        String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("No action was taken because no jobs were selected." + "<BR>" + EOL);
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            return;
        }

        // Perform the update and create an info message with the updated status.
        for (int i = 0; i < jobIDs.length; i++) {
            try {
                Job job = configDB.getJob(jobIDs[i]);
                job.setDisplayInReadOnlyMode(displayInReadOnlyMode);
                configDB.writeJob(job);
                infoMessage.append("Successfully updated job " + jobIDs[i] + "<BR>" + EOL);
            } catch (Exception e) {
                infoMessage.append("Unable to update job " + jobIDs[i] + ":  " + e.getMessage() + "<BR>" + EOL);
            }
        }

        handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
    }

    /**
     * Handles all processing required to create a new job folder.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleCreateFolder(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleCreateFolder()");

        // The user must have permission to manage folders to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "create a new folder.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Get the folder name.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
        if (((folderName != null) && (folderName.length() == 0))) {
            folderName = null;
        }

        // Determine whether to publish this folder in restricted read-only mode.
        boolean displayInReadOnlyMode = false;
        String displayStr = request.getParameter(Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY);
        if ((displayStr == null) || (displayStr.length() == 0)) {
            displayInReadOnlyMode = false;
        } else {
            displayInReadOnlyMode = (displayStr.equalsIgnoreCase("true") || displayStr.equalsIgnoreCase("yes")
                    || displayStr.equalsIgnoreCase("on") || displayStr.equalsIgnoreCase("1"));
        }

        String createString = "Create Folder";
        String cancelString = "Cancel";

        // See if the user has actually made a selection.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((folderName != null) && (confirmStr != null) && (confirmStr.equalsIgnoreCase(createString))) {
            // The user has confirmed that the new folder should be created.
            try {
                JobFolder folder = new JobFolder(folderName, displayInReadOnlyMode, false, null, null, null, null,
                        null, null, null);
                configDB.writeFolder(folder);
                infoMessage.append("Successfully created job folder " + folderName + ".<BR>" + EOL);
            } catch (DatabaseException de) {
                requestInfo.response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, de.getMessage());
                infoMessage
                        .append("ERROR:  Unable to create job folder " + folderName + " -- " + de + "<BR>" + EOL);
            }

            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
        } else if ((confirmStr != null) && confirmStr.equalsIgnoreCase(cancelString)) {
            // The folder creation has been cancelled.
            infoMessage.append("No job folder was created.<BR>" + EOL);
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
        } else {
            // Display a form that allows the user to create a new job folder.
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Create a New Job Folder</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append("  "
                    + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_CREATE_FOLDER) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><SPAN CLASS=\"" + Constants.STYLE_FORM_CAPTION
                    + "\">New Folder Name</SPAN></TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_JOB_FOLDER
                    + "\" SIZE=\"40\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Display In Restricted Read-Only Mode</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append(
                    "      <TD><INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_DISPLAY_IN_READ_ONLY
                            + '"' + (displayInReadOnlyMode ? " CHECKED" : "") + "></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"" + createString + "\">" + EOL);
            htmlBody.append("        <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"" + cancelString + "\">" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles all processing required to remove a job folder.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleDeleteFolder(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleDeleteFolder()");

        // The user must have permission to schedule jobs to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayScheduleJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "delete job folders.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Get the folder name.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
        if (((folderName != null) && (folderName.length() == 0))) {
            folderName = null;
        } else if ((folderName != null) && folderName.equals(Constants.FOLDER_NAME_UNCLASSIFIED)) {
            infoMessage.append(
                    "You cannot delete the " + Constants.FOLDER_NAME_UNCLASSIFIED + " job folder.<BR>" + EOL);
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED,
                    Constants.FOLDER_NAME_UNCLASSIFIED, null);
            return;
        }

        String deleteString = "Delete Folder";
        String cancelString = "Cancel";

        // See if the user has actually made a selection.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((folderName != null) && (confirmStr != null) && (confirmStr.equalsIgnoreCase(deleteString))) {
            // The user has confirmed that the new folder should be created.
            // Determine whether to delete the folder contents as well.
            boolean deleteContents = false;
            String deleteContentsStr = request.getParameter(Constants.SERVLET_PARAM_DELETE_FOLDER_CONTENTS);
            if (deleteContentsStr != null) {
                deleteContents = (deleteContentsStr.equalsIgnoreCase("true")
                        || deleteContentsStr.equalsIgnoreCase("yes") || deleteContentsStr.equalsIgnoreCase("on")
                        || deleteContentsStr.equalsIgnoreCase("1"));
            }

            try {
                configDB.removeFolder(folderName, deleteContents);
                infoMessage.append("Successfully deleted job folder " + folderName + ".<BR>" + EOL);
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED,
                        Constants.FOLDER_NAME_UNCLASSIFIED, null);
            } catch (DatabaseException de) {
                String message = "Unable to delete job folder " + folderName + " -- " + de.getMessage();
                infoMessage.append("ERROR:  " + message + "<BR>" + EOL);
                requestInfo.response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, message);
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            }
        } else if ((folderName != null) && (confirmStr != null) && (confirmStr.equalsIgnoreCase(cancelString))) {
            // The folder creation has been cancelled.
            infoMessage.append("No job folder was deleted.<BR>" + EOL);
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
        } else {
            // Get the list of available job folders.
            JobFolder[] folders = null;
            try {
                folders = configDB.getFolders();
            } catch (DatabaseException de) {
                infoMessage
                        .append("ERROR:  Could not retrieve the list of job " + "folders -- " + de + "<BR>" + EOL);
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
                return;
            }

            if ((folders == null) || (folders.length <= 1)) {
                htmlBody.append(
                        "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete a Job Folder</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("No custom job folders have been defined in the " + "SLAMD server." + EOL);
                return;
            }

            // Display a form that allows the user to delete a job folder.
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete a Job Folder</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Please choose the job folder to delete." + EOL);
            htmlBody.append("Note that only empty folders may be removed." + EOL);
            htmlBody.append("To remove a folder that is not empty, first delete "
                    + "or move any jobs contained in that folder." + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append("  "
                    + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_DELETE_FOLDER) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Folder to Delete</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <SELECT NAME=\"" + Constants.SERVLET_PARAM_JOB_FOLDER + "\">" + EOL);
            for (int i = 0; i < folders.length; i++) {
                if (folders[i].getFolderName().equals(Constants.FOLDER_NAME_UNCLASSIFIED)) {
                    continue;
                }

                htmlBody.append("          <OPTION VALUE=\"" + folders[i].getFolderName() + "\">"
                        + folders[i].getFolderName() + EOL);
            }

            htmlBody.append("        </SELECT>" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Delete Folder Contents</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"CHECKBOX\" NAME=\""
                    + Constants.SERVLET_PARAM_DELETE_FOLDER_CONTENTS + "\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"" + deleteString + "\">" + EOL);
            htmlBody.append("        <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"" + cancelString + "\">" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles all processing required to move one or more jobs to a specified
     * job folder.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleMoveJobs(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleMoveJobs()");

        // The user must have permission to schedule jobs to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayScheduleJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "move jobs.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Get the job IDs of the jobs to move.  If there were none, then display
        // an error message.
        String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("ERROR:  No jobs selected to move.<BR>" + EOL);
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
            return;
        }

        // Get the name of the folder into which the jobs are to be moved.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
        if ((folderName != null) && (folderName.length() == 0)) {
            folderName = null;
        }

        String moveStr = "Move Jobs";
        String cancelStr = "Cancel";

        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);

        // See if the user has confirmed that the job information should be moved.
        if ((folderName != null) && (confirmStr != null) && (confirmStr.equalsIgnoreCase(moveStr))) {
            // The user wants to move the jobs, so do it.
            for (int i = 0; i < jobIDs.length; i++) {
                try {
                    configDB.moveJob(jobIDs[i], folderName);
                    infoMessage.append("Successfully moved job " + jobIDs[i] + "<BR>" + EOL);
                } catch (DatabaseException de) {
                    infoMessage.append("ERROR while moving job " + jobIDs[i] + ":  " + de + "<BR>" + EOL);
                }
            }

            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
        } else if ((folderName != null) && (confirmStr != null) && (confirmStr.equalsIgnoreCase(cancelStr))) {
            infoMessage.append("The selected jobs were not moved.<BR>" + EOL);
            handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
        } else {
            // Get the list of available job folders.
            JobFolder[] folders = null;
            try {
                folders = configDB.getFolders();
            } catch (DatabaseException de) {
                infoMessage
                        .append("ERROR:  Could not retrieve the list of job " + "folders -- " + de + "<BR>" + EOL);
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
                return;
            }

            if (folders.length == 0) {
                infoMessage.append("ERROR:  There are no custom folders defined.<BR>" + EOL);
                handleViewJob(requestInfo, Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED, null, null);
                return;
            }

            // Display a form that allows the user to choose the new folder.
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Move Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Please specify the new folder for the following jobs." + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);
            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  <LI>" + jobIDs[i] + "</LI>" + EOL);
            }
            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_MOVE) + EOL);
            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append(' ' + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }
            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><SPAN CLASS=\"" + Constants.STYLE_FORM_CAPTION
                    + "\">New Folder Name</SPAN></TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <SELECT NAME=\"" + Constants.SERVLET_PARAM_JOB_FOLDER + "\">" + EOL);

            for (int i = 0; i < folders.length; i++) {
                htmlBody.append("          <OPTION VALUE=\"" + folders[i].getFolderName() + "\">"
                        + folders[i].getFolderName() + EOL);
            }

            htmlBody.append("        </SELECT>" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"" + moveStr + "\">" + EOL);
            htmlBody.append("        <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"" + cancelStr + "\">" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles the work of adding a set of jobs to a virtual job folder.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleAddToVirtualFolder(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleAddToVirtualFolder()");

        // The user must have permission to schedule jobs to do anything in this
        // section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayScheduleJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "modify virtual job folders");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Make sure that at least one job ID was provided.  If not, then display
        // an error.
        String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_JOB_ID);
        if ((jobIDs == null) || (jobIDs.length == 0)) {
            infoMessage.append("ERROR:  No jobs specified<BR>" + EOL);
            htmlBody.append("You must specify at least one job to be added to a " + "virtual folder." + EOL);
            return;
        }

        // See if a folder name was provided.  If so, then try to add the specified
        // jobs to the folder.  If not, then prompt the user for a folder name.
        String folderName = request.getParameter(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER);
        if ((folderName != null) && folderName.equals(Constants.FOLDER_NAME_CREATE_NEW_FOLDER)) {
            folderName = request.getParameter(Constants.SERVLET_PARAM_NEW_FOLDER_NAME);
        }

        if ((folderName != null) && (folderName.length() > 0)) {
            // Add the specified list of job IDs to the virtual folder, or create a
            // new folder if necessary.
            try {
                boolean created = false;
                JobFolder folder = configDB.getVirtualFolder(folderName);
                if (folder == null) {
                    folder = new JobFolder(folderName, false, true, null, null, null, jobIDs, null, null, null);
                    created = true;
                } else {
                    for (int i = 0; i < jobIDs.length; i++) {
                        folder.addJobID(jobIDs[i]);
                    }
                }

                configDB.writeVirtualFolder(folder);

                if (created) {
                    infoMessage.append("Successfully created virtual job folder \"" + folderName + "\"<BR>" + EOL);
                } else {
                    infoMessage.append("Successfully updated virtual job folder \"" + folderName + "\"<BR>" + EOL);
                }
            } catch (Exception e) {
                infoMessage.append("Unable to update virtual job folder \"" + folderName + "\" -- " + e.getMessage()
                        + "<BR>" + EOL);
            }

            handleViewVirtualFolder(requestInfo, folderName);
            return;
        } else {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                    + "\">Add Jobs to a Virtual Folder</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);

            // Get the list of virtual job folders that are defined in the SLAMD
            // server.
            JobFolder[] folders;
            try {
                folders = configDB.getVirtualFolders();
            } catch (DatabaseException de) {
                infoMessage.append("ERROR:  Unable to retrieve the list of virtual " + "job folders -- "
                        + de.getMessage() + "<BR>" + EOL);
                htmlBody.append("An error occurred while attempting to retrieve the "
                        + "list of virtual job folders that have been defined " + "in the SLAMD server." + EOL);
                return;
            }

            htmlBody.append("Choose the virtual folder to which the jobs are to be "
                    + "added, or choose to create a new virtual job folder" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MASS_OP)
                            + EOL);
            htmlBody.append("  "
                    + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_ADD_TO_VIRTUAL_FOLDER)
                    + EOL);

            for (int i = 0; i < jobIDs.length; i++) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_ID, jobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            if ((folders != null) && (folders.length > 0)) {
                htmlBody.append("  <SELECT NAME=\"" + Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER + "\">" + EOL);
                htmlBody.append("    <OPTION VALUE=\"" + Constants.FOLDER_NAME_CREATE_NEW_FOLDER + "\">"
                        + Constants.FOLDER_NAME_CREATE_NEW_FOLDER + EOL);
                for (int i = 0; i < folders.length; i++) {
                    htmlBody.append("    <OPTION VALUE=\"" + folders[i].getFolderName() + "\">"
                            + folders[i].getFolderName() + EOL);
                }
                htmlBody.append("  </SELECT>" + EOL);
                htmlBody.append("  <BR>" + EOL);
            } else {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_VIRTUAL_JOB_FOLDER,
                        Constants.FOLDER_NAME_CREATE_NEW_FOLDER) + EOL);
                htmlBody.append("  No virtual job folders have been defined in " + "the SLAMD server.<BR>" + EOL);
            }

            htmlBody.append("  New Virtual Folder Name:  " + EOL);
            htmlBody.append("  <INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_NEW_FOLDER_NAME
                    + "\" SIZE=\"40\">" + EOL);
            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Add Jobs\">" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles the work of performing some operation on multiple optimizing jobs
     * at the same time.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleMassOptimizing(RequestInfo requestInfo) {
        HttpServletRequest request = requestInfo.request;

        // Get the job IDs of the optimizing jobs on which to perform the operation.
        String[] jobIDs = request.getParameterValues(Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID);

        // Determine which mass operation the user wishes to perform.
        String opString = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
        if (opString == null) {
            return;
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_SELECT_ALL)
                || opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DESELECT_ALL)) {
            handleViewOptimizing(requestInfo, true);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DELETE)) {
            handleMassOptimizingDelete(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_MOVE)) {
            handleMassOptimizingMove(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_PUBLISH_JOBS)) {
            handleMassOptimizingPublish(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_DEPUBLISH_JOBS)) {
            handleMassOptimizingDepublish(requestInfo, jobIDs);
        } else if (opString.equalsIgnoreCase(Constants.SUBMIT_STRING_GENERATE_REPORT)) {
            handleGenerateReport(requestInfo);
        }
    }

    /**
     * Handles the work of deleting multiple optimizing jobs at the same time.
     *
     * @param  requestInfo       The state information for this request.
     * @param  optimizingJobIDs  The job IDs of the optimizing jobs to be
     *                           deleted.
     */
    static void handleMassOptimizingDelete(RequestInfo requestInfo, String[] optimizingJobIDs) {
        logMessage(requestInfo, "In handleMassOptimizingDelete()");

        // The user must have permission to delete jobs to access this section.
        if (!requestInfo.mayDeleteJob) {
            logMessage(requestInfo, "No mayDeleteJob permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "delete job information.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder infoMessage = requestInfo.infoMessage;
        StringBuilder htmlBody = requestInfo.htmlBody;

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((optimizingJobIDs == null) || (optimizingJobIDs.length == 0)) {
            infoMessage.append("No optimizing jobs specified to delete.<BR>" + EOL);
            handleViewOptimizing(requestInfo, true);
            return;
        }

        // See if the user has provided confirmation.  If so, then delete the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                    + "\">Delete Selected Optimizing Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure that you want to delete the selected " + "optimizing jobs?" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < optimizingJobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_OPTIMIZING, Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID,
                        optimizingJobIDs[i], optimizingJobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_MASS_OPTIMIZING) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_DELETE) + EOL);

            String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);
            if (folderName != null) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_FOLDER, folderName) + EOL);
            }

            for (int i = 0; i < optimizingJobIDs.length; i++) {
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID, optimizingJobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <BR>" + EOL);
            htmlBody.append(
                    "  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_OPTIMIZING_JOB_INCLUDE_ITERATIONS
                            + "\" CHECKED>Delete all iterations of the optimizing jobs" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"5\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            for (int i = 0; i < optimizingJobIDs.length; i++) {
                try {
                    boolean includeIterations = false;
                    String includeStr = request
                            .getParameter(Constants.SERVLET_PARAM_OPTIMIZING_JOB_INCLUDE_ITERATIONS);
                    includeIterations = ((includeStr != null)
                            && (includeStr.equalsIgnoreCase("true") || includeStr.equalsIgnoreCase("yes")
                                    || includeStr.equalsIgnoreCase("on") || includeStr.equalsIgnoreCase("1")));
                    if (includeIterations) {
                        OptimizingJob optimizingJob = getOptimizingJob(optimizingJobIDs[i]);
                        if (optimizingJob == null) {
                            infoMessage.append("ERROR:  Unable to retrieve optimizing job " + optimizingJobIDs[i]
                                    + "<BR>" + EOL);
                            handleViewOptimizing(requestInfo, true);
                            return;
                        }

                        Job[] iterations = optimizingJob.getAssociatedJobs();
                        if ((iterations != null) && (iterations.length > 0)) {
                            for (int j = 0; j < iterations.length; j++) {
                                try {
                                    configDB.removeJob(iterations[j].getJobID());
                                } catch (Exception e) {
                                    infoMessage.append("Unable to remove optimizing job " + "iteration "
                                            + iterations[j].getJobID() + " -- " + e);
                                }
                            }
                        }

                        Job reRunIteration = optimizingJob.getReRunIteration();
                        if (reRunIteration != null) {
                            try {
                                configDB.removeJob(reRunIteration.getJobID());
                            } catch (Exception e) {
                                infoMessage.append("Unable to remove optimizing job re-run " + "iteration "
                                        + reRunIteration.getJobID() + " -- " + e);
                            }
                        }
                    }

                    configDB.removeOptimizingJob(optimizingJobIDs[i]);
                    infoMessage.append("Deleted optimizing job " + optimizingJobIDs[i] + ".<BR>" + EOL);
                } catch (Exception e) {
                    infoMessage.append(
                            "Unable to delete optimizing job " + optimizingJobIDs[i] + ":  " + e + "<BR>" + EOL);
                }
            }

            handleViewOptimizing(requestInfo, true);
        } else {
            infoMessage.append("No optimizing jobs were deleted.<BR>" + EOL);
            handleViewOptimizing(requestInfo, true);
        }
    }

    /**
     * Handles the work of moving multiple optimizing jobs.
     *
     * @param  requestInfo       The state information for this request.
     * @param  optimizingJobIDs  The job IDs of the optimizing jobs to be moved.
     */
    static void handleMassOptimizingMove(RequestInfo requestInfo, String[] optimizingJobIDs) {
        logMessage(requestInfo, "In handleMassOptimizingMove()");

        // The user must have permission to manage folders to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "move job information.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder infoMessage = requestInfo.infoMessage;
        StringBuilder htmlBody = requestInfo.htmlBody;

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((optimizingJobIDs == null) || (optimizingJobIDs.length == 0)) {
            infoMessage.append("No optimizing jobs specified to move.<BR>" + EOL);
            handleViewOptimizing(requestInfo, true);
            return;
        }

        // See if the user has provided confirmation.  If so, then delete the jobs
        // or don't based on the results of the confirmation.  If no confirmation
        // has been provided, then request it.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("Move Jobs")) && (!confirmStr.equalsIgnoreCase("Cancel")))) {
            // Get the set of job folders defined in the configuration directory.  If
            // there are none, then return an error.
            JobFolder[] folders = null;
            String failReason = "No job folders have been created.";
            try {
                folders = configDB.getFolders();
            } catch (DatabaseException de) {
                failReason = de.getMessage();
            }

            if ((folders == null) || (folders.length == 0)) {
                infoMessage.append("Unable to move the selected optimizing jobs:  " + failReason + "<BR>" + EOL);
                handleViewOptimizing(requestInfo, true);
                return;
            }

            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                    + "\">Move Selected Optimizing Jobs</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Please specify the destination folder for the " + "optimizing jobs" + EOL);
            htmlBody.append("<BR>" + EOL);
            htmlBody.append("<UL>" + EOL);

            for (int i = 0; i < optimizingJobIDs.length; i++) {
                String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                        Constants.SERVLET_SECTION_JOB_VIEW_OPTIMIZING, Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID,
                        optimizingJobIDs[i], optimizingJobIDs[i]);
                htmlBody.append("  <LI>" + link + "</LI>" + EOL);
            }

            htmlBody.append("</UL>" + EOL);
            htmlBody.append("<BR>" + EOL);

            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_MASS_OPTIMIZING) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBMIT, Constants.SUBMIT_STRING_MOVE) + EOL);

            for (int i = 0; i < optimizingJobIDs.length; i++) {
                htmlBody.append("  "
                        + generateHidden(Constants.SERVLET_PARAM_OPTIMIZING_JOB_ID, optimizingJobIDs[i]) + EOL);
            }

            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"5\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD>Destination Folder</TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>" + EOL);
            htmlBody.append("        <SELECT NAME=\"" + Constants.SERVLET_PARAM_JOB_FOLDER + "\">" + EOL);
            for (int i = 0; i < folders.length; i++) {
                htmlBody.append("          <OPTION VALUE=\"" + folders[i].getFolderName() + "\">"
                        + folders[i].getFolderName() + EOL);
            }
            htmlBody.append("        </SELECT>" + EOL);
            htmlBody.append("      </TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"CHECKBOX\" NAME=\""
                    + Constants.SERVLET_PARAM_OPTIMIZING_JOB_INCLUDE_ITERATIONS + "\" CHECKED></TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD>Move all iterations of the optimizing " + "jobs</TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Move Jobs\"></TD>" + EOL);
            htmlBody.append("      <TD>&nbsp;</TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Cancel\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("Move Jobs")) {
            String folderName = request.getParameter(Constants.SERVLET_PARAM_JOB_FOLDER);

            for (int i = 0; i < optimizingJobIDs.length; i++) {
                try {
                    boolean includeIterations = false;
                    String includeStr = request
                            .getParameter(Constants.SERVLET_PARAM_OPTIMIZING_JOB_INCLUDE_ITERATIONS);
                    includeIterations = ((includeStr != null)
                            && (includeStr.equalsIgnoreCase("true") || includeStr.equalsIgnoreCase("yes")
                                    || includeStr.equalsIgnoreCase("on") || includeStr.equalsIgnoreCase("1")));
                    if (includeIterations) {
                        OptimizingJob optimizingJob = getOptimizingJob(optimizingJobIDs[i]);
                        if (optimizingJob == null) {
                            infoMessage.append("ERROR:  Unable to retrieve optimizing job " + optimizingJobIDs[i]
                                    + "<BR>" + EOL);
                            handleViewOptimizing(requestInfo, true);
                            return;
                        }

                        Job[] iterations = optimizingJob.getAssociatedJobs();
                        if ((iterations != null) && (iterations.length > 0)) {
                            for (int j = 0; j < iterations.length; j++) {
                                try {
                                    configDB.moveJob(iterations[j].getJobID(), folderName);
                                } catch (Exception e) {
                                    infoMessage.append("Unable to move optimizing job " + "iteration "
                                            + iterations[j].getJobID() + " -- " + e);
                                }
                            }
                        }

                        Job reRunIteration = optimizingJob.getReRunIteration();
                        if (reRunIteration != null) {
                            try {
                                configDB.moveJob(reRunIteration.getJobID(), folderName);
                            } catch (Exception e) {
                                infoMessage.append("Unable to move optimizing job re-run " + "iteration "
                                        + reRunIteration.getJobID() + " -- " + e);
                            }
                        }
                    }

                    configDB.moveOptimizingJob(optimizingJobIDs[i], folderName);
                    infoMessage.append("Moved optimizing job " + optimizingJobIDs[i] + ".<BR>" + EOL);
                } catch (Exception e) {
                    infoMessage.append(
                            "Unable to move optimizing job " + optimizingJobIDs[i] + ":  " + e + "<BR>" + EOL);
                }
            }

            handleViewOptimizing(requestInfo, true);
        } else {
            infoMessage.append("No optimizing jobs were moved.<BR>" + EOL);
            handleViewOptimizing(requestInfo, true);
        }
    }

    /**
     * Handles the work of publishing information about multiple optimizing jobs.
     *
     * @param  requestInfo       The state information for this request.
     * @param  optimizingJobIDs  The job IDs of the optimizing jobs to be
     *                           published.
     */
    static void handleMassOptimizingPublish(RequestInfo requestInfo, String[] optimizingJobIDs) {
        logMessage(requestInfo, "In handleMassOptimizingPublish()");

        // The user must have permission to manage folders to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "publish job information.");
            return;
        }

        // Get the important state variables for this request.
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((optimizingJobIDs == null) || (optimizingJobIDs.length == 0)) {
            infoMessage.append("No optimizing jobs specified to publish.<BR>" + EOL);
            handleViewOptimizing(requestInfo, true);
            return;
        }

        // Iterate through the jobs and mark each for publication.
        for (int i = 0; i < optimizingJobIDs.length; i++) {
            try {
                OptimizingJob optimizingJob = getOptimizingJob(optimizingJobIDs[i]);
                optimizingJob.setDisplayInReadOnlyMode(true);
                configDB.writeOptimizingJob(optimizingJob);
                infoMessage.append("Marked optimizing job " + optimizingJobIDs[i] + " for publication.<BR>" + EOL);
            } catch (Exception e) {
                infoMessage.append(
                        "Unable to publish optimizing job " + optimizingJobIDs[i] + ":  " + e + "<BR>" + EOL);
            }
        }

        handleViewOptimizing(requestInfo, true);
    }

    /**
     * Handles the work of de-publishing information about multiple optimizing
     * jobs.
     *
     * @param  requestInfo       The state information for this request.
     * @param  optimizingJobIDs  The job IDs of the optimizing jobs to be
     *                           de-published.
     */
    static void handleMassOptimizingDepublish(RequestInfo requestInfo, String[] optimizingJobIDs) {
        logMessage(requestInfo, "In handleMassOptimizingPublish()");

        // The user must have permission to manage folders to access this section.
        if (!requestInfo.mayManageFolders) {
            logMessage(requestInfo, "No mayManageFolders permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "de-publish job information.");
            return;
        }

        // Get the important state variables for this request.
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Make sure that at least one job ID was specified.  If not, then print an
        // error message and view the appropriate set of jobs.
        if ((optimizingJobIDs == null) || (optimizingJobIDs.length == 0)) {
            infoMessage.append("No optimizing jobs specified to de-publish.<BR>" + EOL);
            handleViewOptimizing(requestInfo, true);
            return;
        }

        // Iterate through the jobs and mark each for de-publication.
        for (int i = 0; i < optimizingJobIDs.length; i++) {
            try {
                OptimizingJob optimizingJob = getOptimizingJob(optimizingJobIDs[i]);
                optimizingJob.setDisplayInReadOnlyMode(false);
                configDB.writeOptimizingJob(optimizingJob);
                infoMessage
                        .append("Marked optimizing job " + optimizingJobIDs[i] + " for de-publication.<BR>" + EOL);
            } catch (Exception e) {
                infoMessage.append(
                        "Unable to de-publish optimizing job " + optimizingJobIDs[i] + ":  " + e + "<BR>" + EOL);
            }
        }

        handleViewOptimizing(requestInfo, true);
    }

    /**
     * Handles all processing required to import data into the SLAMD server.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleDataImport(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleDataImport()");

        // The user must have export job data permission to see anything here.
        if (!requestInfo.mayExportJobData) {
            logMessage(requestInfo, "No mayExportJobData permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "import job data");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        HttpServletResponse response = requestInfo.response;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // See if the user has told us where the import file is.  If so, then
        // process it.  If not, then ask them for the location.
        String importFile = request.getParameter(Constants.SERVLET_PARAM_DATA_IMPORT_FILE);
        if ((importFile == null) || (importFile.length() == 0)) {
            // We don't know where the import file is.  Ask the user.
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Import Job Data</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Please provide the absolute path (on the SLAMD server "
                    + "system) to the location of the file containing the " + "data to import." + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_IMPORT_JOB_DATA) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  Import File:  " + EOL);
            htmlBody.append("  <INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_DATA_IMPORT_FILE
                    + "\" SIZE=\"80\">" + EOL);
            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Import Data\">" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else {
            // We have the import file, so we can process it.  However, we have to do
            // some trickery here in order to give the user feedback in real-time so
            // they can see that the import actually is going on.  Therefore, we will
            // generate all the HTML ourselves, including the header, the sidebar, and
            // the copyright notice.
            PrintWriter writer = null;
            try {
                writer = response.getWriter();
            } catch (IOException ioe) {
                infoMessage.append("Unable to obtain output writer -- " + ioe + "<BR>" + EOL);
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Import Failed</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append(
                        "An error occurred that prevented the import from " + "being processed successfully.");
                return;
            }

            requestInfo.generateHTML = false;
            response.setContentType("text/html");

            // Generate the HTML header and print it out.  Then replace the HTML
            // body with an empty string buffer.
            writer.println(generateHTMLHeader(requestInfo));
            writer.flush();

            // Hand the import off to the config handler to process it.
            FileInputStream inputStream = null;
            try {
                writer.println("<!-- START IMPORT -->");
                inputStream = new FileInputStream(importFile);
                configDB.importFolderData(inputStream, writer, true);
                writer.println("<!-- END IMPORT -->");
            } catch (Exception e) {
                writer.println("<!-- END IMPORT -->");
                writer.println("<!-- IMPORT ERROR:  " + JobClass.stackTraceToString(e) + " -->");
                writer.println("Unexpected error processing import file:  " + JobClass.stackTraceToString(e));
                writer.flush();

                try {
                    inputStream.close();
                } catch (Exception e2) {
                }
            }

            // Generate the copyright notice and print it out.
            writer.println(generateHTMLFooter(requestInfo));
            writer.flush();
        }
    }

    /**
     * Handles all processing required to export data from the SLAMD server.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleDataExport(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleDataExport()");

        // The user must have export job data permission to see anything here.
        if (!requestInfo.mayExportJobData) {
            logMessage(requestInfo, "No mayExportJobData permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "export job data");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        HttpServletResponse response = requestInfo.response;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // See if the user has selected which information to export.
        String submitStr = request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
        if ((submitStr != null) && submitStr.equals(Constants.SUBMIT_STRING_EXPORT)) {
            // The user wants to actually export the data.  Figure out what that
            // includes and do the export.
            boolean exportAll = false;
            String exportChoice = request.getParameter(Constants.SERVLET_PARAM_EXPORT_CHOICE);
            if ((exportChoice != null) && exportChoice.equals(Constants.EXPORT_CHOICE_ALL)) {
                exportAll = true;
            }

            ArrayList<String> realList = new ArrayList<String>();
            ArrayList<String> virtualList = new ArrayList<String>();
            ArrayList<String> jobGroupList = new ArrayList<String>();
            if (!exportAll) {
                String[] realArray = request.getParameterValues(Constants.SERVLET_PARAM_EXPORT_REAL_FOLDER);
                for (int i = 0; ((realArray != null) && (i < realArray.length)); i++) {
                    realList.add(realArray[i]);
                }

                String[] virtualArray = request.getParameterValues(Constants.SERVLET_PARAM_EXPORT_VIRTUAL_FOLDER);
                for (int i = 0; ((virtualArray != null) && (i < virtualArray.length)); i++) {
                    virtualList.add(virtualArray[i]);
                }

                String[] jobGroupArray = request.getParameterValues(Constants.SERVLET_PARAM_EXPORT_JOB_GROUP);
                for (int i = 0; ((jobGroupArray != null) && (i < jobGroupArray.length)); i++) {
                    jobGroupList.add(jobGroupArray[i]);
                }
            } else {
                try {
                    JobFolder[] realFolders = configDB.getFolders();
                    JobFolder[] virtualFolders = configDB.getVirtualFolders();
                    JobGroup[] jobGroups = configDB.getSummaryJobGroups();
                    for (int i = 0; i < realFolders.length; i++) {
                        realList.add(realFolders[i].getFolderName());
                    }
                    for (int i = 0; i < virtualFolders.length; i++) {
                        virtualList.add(virtualFolders[i].getFolderName());
                    }
                    for (int i = 0; i < jobGroups.length; i++) {
                        jobGroupList.add(jobGroups[i].getName());
                    }
                } catch (DatabaseException de) {
                    infoMessage.append(
                            "Unable to retrieve the list of available " + "folders -- " + de + "<BR>" + EOL);
                    htmlBody.append(
                            "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Export Failed</SPAN>" + EOL);
                    htmlBody.append("<BR><BR>" + EOL);
                    htmlBody.append(
                            "An error occurred that prevented the export from " + "being processed successfully.");
                    return;
                }
            }

            OutputStream outputStream;
            try {
                outputStream = response.getOutputStream();
            } catch (IOException ioe) {
                infoMessage.append("Unable to obtain output writer -- " + ioe + "<BR>" + EOL);
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Export Failed</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append(
                        "An error occurred that prevented the export from " + "being processed successfully.");
                return;
            }

            // From this point on, we are committed to actually generating the output
            // file.  If an error occurs, then there's not much that can be done
            // about it.  We'll log it to the SLAMD log but we don't really get
            // much
            requestInfo.generateHTML = false;
            requestInfo.response.setContentType("application/x-slamd-data-export");
            requestInfo.response.addHeader("Content-Disposition",
                    "filename=\"slamd_data_export_data." + dateFormat.format(new Date()) + '"');

            String[] realFolderNames = new String[realList.size()];
            realList.toArray(realFolderNames);

            String[] virtualFolderNames = new String[virtualList.size()];
            virtualList.toArray(virtualFolderNames);

            String[] jobGroupNames = new String[jobGroupList.size()];
            jobGroupList.toArray(jobGroupNames);

            try {
                configDB.exportFolderData(realFolderNames, virtualFolderNames, jobGroupNames, outputStream);
            } catch (Exception e) {
                slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Export of SLAMD server data failed:  " + e);
                slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e));
            }
        } else if ((submitStr != null) && submitStr.equals(Constants.SUBMIT_STRING_CANCEL)) {
            // The user cancelled the export.
            infoMessage.append("Job data export cancelled<BR>" + EOL);
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Export Cancelled</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("The job data export was cancelled.");
        } else {
            // The user has not yet specified what to export.  Show them a form to
            // allow them to choose what they want included.
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Export Job Data</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Choose which information you wish to export");
            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_EXPORT_JOB_DATA) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <SELECT NAME=\"" + Constants.SERVLET_PARAM_EXPORT_CHOICE + "\">" + EOL);
            htmlBody.append("    <OPTION VALUE=\"" + Constants.EXPORT_CHOICE_ALL + "\">"
                    + Constants.EXPORT_CHOICE_ALL + EOL);
            htmlBody.append("    <OPTION VALUE=\"" + Constants.EXPORT_CHOICE_SELECTED + "\">"
                    + Constants.EXPORT_CHOICE_SELECTED + EOL);
            htmlBody.append("  </SELECT>" + EOL);
            htmlBody.append("  <BR><BR>" + EOL);

            try {
                JobFolder[] realFolders = configDB.getFolders();
                for (int i = 0; i < realFolders.length; i++) {
                    htmlBody.append(
                            "  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_REAL_FOLDER
                                    + "\" VALUE=\"" + realFolders[i].getFolderName() + "\">Real Job Folder \""
                                    + realFolders[i].getFolderName() + "\"<BR>" + EOL);
                }
            } catch (DatabaseException de) {
                infoMessage.append("ERROR:  Unable to retrieve the list of defined " + "real job folders -- " + de
                        + "<BR>" + EOL);
            }

            try {
                JobFolder[] virtualFolders = configDB.getVirtualFolders();
                for (int i = 0; i < virtualFolders.length; i++) {
                    htmlBody.append(
                            "  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_VIRTUAL_FOLDER
                                    + "\" VALUE=\"" + virtualFolders[i].getFolderName() + "\">Virtual Job Folder \""
                                    + virtualFolders[i].getFolderName() + "\"<BR>" + EOL);
                }
            } catch (DatabaseException de) {
                infoMessage.append("ERROR:  Unable to retrieve the list of defined " + "virtual job folders -- "
                        + de + "<BR>" + EOL);
            }

            try {
                JobGroup[] jobGroups = configDB.getSummaryJobGroups();
                for (int i = 0; i < jobGroups.length; i++) {
                    htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_EXPORT_JOB_GROUP
                            + "\" VALUE=\"" + jobGroups[i].getName() + "\">Job Group \"" + jobGroups[i].getName()
                            + "\"<BR>" + EOL);
                }
            } catch (DatabaseException de) {
                infoMessage.append(
                        "ERROR:  Unable to retrieve the list of defined " + "job groups -- " + de + "<BR>" + EOL);
            }

            htmlBody.append("  <BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_EXPORT + "\">" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_SUBMIT + "\" VALUE=\""
                    + Constants.SUBMIT_STRING_CANCEL + "\">" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles all processing require to migrate job data from a SLAMD 1.x-style
     * configuration directory into the new configuration database.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleMigrateData(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleMigrateData()");

        // The user must have export permission to see anything here.
        if (!requestInfo.mayExportJobData) {
            logMessage(requestInfo, "No mayExportJobData permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "migrate job data.");
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        HttpServletResponse response = requestInfo.response;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // Create parameters that may be used to specify information about the
        // directory server from which to migrate the data.
        BooleanParameter useSSLParameter = new BooleanParameter("use_ssl", "Use SSL",
                "Indicates whether to use SSL to communicate " + "with the directory server.", false);
        IntegerParameter portParameter = new IntegerParameter("ldap_port", "Directory Server Port",
                "The port of the directory server containing " + "the data to be migrated.", true, 389, true, 1,
                true, 65535);
        PasswordParameter bindPWParameter = new PasswordParameter("bind_pw", "Bind Password",
                "The password to use to bind to the directory " + "server.", true, "");
        StringParameter addressParameter = new StringParameter("ldap_host", "Directory Server Address",
                "The address of the directory server containing " + "the data to be migrated.", true, "");
        StringParameter baseDNParameter = new StringParameter("base_dn", "Configuration Base DN",
                "The DN of the entry that serves as the base " + "for the SLAMD configuration data in the "
                        + "directory server.",
                true, "");
        StringParameter bindDNParameter = new StringParameter("bind_dn", "Bind DN",
                "The DN to use to bind to the directory server.", true, "");

        Parameter[] dsParams = new Parameter[] { addressParameter, portParameter, useSSLParameter, bindDNParameter,
                bindPWParameter, baseDNParameter };

        // See if we have an adequate configuration for the directory server.
        boolean validDSConfig = true;
        String submitStr = request.getParameter(Constants.SERVLET_PARAM_SERVER_INFO_SUBMITTED);
        if ((submitStr == null) || (submitStr.length() == 0)) {
            validDSConfig = false;
        } else {
            for (int i = 0; i < dsParams.length; i++) {
                try {
                    dsParams[i].htmlInputFormToValue(request);
                } catch (Exception e) {
                    infoMessage.append("Invalid value for " + dsParams[i].getDisplayName() + ":  " + e.getMessage()
                            + "<BR>" + EOL);
                    validDSConfig = false;
                }
            }
        }

        // If we think that a valid directory configuration was provided, then try
        // to use it to get a list of all the folders in the directory server.
        DSMigrator migrator = null;
        String[] folderNames = null;
        if (validDSConfig) {
            try {
                migrator = new DSMigrator(slamdServer, addressParameter.getStringValue(),
                        portParameter.getIntValue(), useSSLParameter.getBooleanValue(),
                        bindDNParameter.getStringValue(), bindPWParameter.getStringValue(),
                        baseDNParameter.getStringValue());
                folderNames = migrator.getFolderNames();
            } catch (LDAPException le) {
                infoMessage.append("Unable to obtain a list of job folders from the "
                        + "configuration directory using the provided " + "settings:  LDAP Result code "
                        + le.getLDAPResultCode() + " (" + le + ")<BR>" + EOL);
                validDSConfig = false;
            } catch (Exception e) {
                infoMessage.append("Unable to obtain a list of job folders from the "
                        + "configuration directory using the provided " + "settings:  " + e.getMessage() + "<BR>"
                        + EOL);
                validDSConfig = false;
            }
        }

        // If we don't have a valid directory configuration, then prompt the user to
        // provide one.
        if (!validDSConfig) {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                    + "\">Migrate Data from Configuration Directory</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);

            htmlBody.append("Please provide the following information about the "
                    + "SLAMD 1.x configuration directory containing the "
                    + "data to migrate to the current configuration database:" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                    + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MIGRATE)
                            + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SERVER_INFO_SUBMITTED, "1") + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  <TABLE BORDER=\"0\">" + EOL);

            for (int i = 0; i < dsParams.length; i++) {
                String star;
                if (dsParams[i].isRequired()) {
                    star = "<SPAN CLASS=\"" + Constants.STYLE_WARNING_TEXT + "\">*</SPAN>";
                } else {
                    star = "";
                }

                htmlBody.append("  <TR>" + EOL);
                htmlBody.append("    <TD>" + dsParams[i].getDisplayName() + star + "</TD>" + EOL);
                htmlBody.append("    <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("    <TD>" + dsParams[i].getHTMLInputForm(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX)
                        + "</TD>" + EOL);
                htmlBody.append("  </TR>" + EOL);
            }

            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("  <BR><BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Next >\">" + EOL);
            htmlBody.append("</FORM>" + EOL);
            return;
        }

        // Check to see if the user has specified which folders to migrate.  If so,
        // then try to perform the migration.
        String migrateFoldersStr = request.getParameter(Constants.SERVLET_PARAM_MIGRATE_FOLDERS);
        if ((migrateFoldersStr != null) && (migrateFoldersStr.length() > 0)) {
            String[] migrateFolders;
            if (migrateFoldersStr.equals(Constants.MIGRATE_FOLDERS_ALL)) {
                migrateFolders = folderNames;
            } else if (migrateFoldersStr.equals(Constants.MIGRATE_FOLDERS_SELECTED)) {
                migrateFolders = request.getParameterValues(Constants.SERVLET_PARAM_JOB_FOLDER);
            } else {
                migrateFolders = new String[0];
            }

            if ((migrateFolders != null) && (migrateFolders.length > 0)) {
                // At this point, we're going to commit to the migration.  In order to
                // be more interactive, we'll use a print writer to send data directly
                // to the browser rather than batching it all up and sending it as one
                // blob.
                requestInfo.generateHTML = false;
                PrintWriter writer = null;
                try {
                    writer = response.getWriter();
                } catch (Exception e) {
                }

                writer.println(generateHTMLHeader(requestInfo));
                writer.println("<!-- START INFO MESSAGE -->" + EOL);
                writer.println(generateWarning(requestInfo.infoMessage.toString()));
                writer.println("<!-- END INFO MESSAGE -->" + EOL + EOL);
                writer.println("<!-- START MAIN BODY -->" + EOL);

                writer.println("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                        + "\">Migrate Data from Configuration Directory</SPAN>");
                writer.println("<BR><BR>" + EOL);
                writer.println("Beginning data migration from directory server " + addressParameter.getStringValue()
                        + ':' + portParameter.getIntValue() + '.');
                writer.println("This may take some time to complete.");
                writer.println("<BR><BR");

                boolean completeSuccess = true;
                for (int i = 0; i < migrateFolders.length; i++) {
                    writer.println("<B><FONT SIZE=\"+1\">Beginning Migration for " + "Folder " + migrateFolders[i]
                            + "</FONT></B>");
                    writer.println("<BR>");
                    writer.flush();

                    writer.println("<PRE>");

                    boolean successful = migrator.migrateJobFolder(migrateFolders[i], writer);
                    if (!successful) {
                        completeSuccess = false;
                    }

                    writer.println("</PRE>");
                    writer.println("All processing complete for folder " + migrateFolders[i] + '.');
                    if (!successful) {
                        writer.println("<BR>");
                        writer.println("<SPAN CLASS=\"" + Constants.STYLE_WARNING_TEXT
                                + "\">One or more errors occurred while processing " + "this folder</SPAN>");
                    }

                    writer.println("<BR><BR>");
                    writer.flush();
                }

                writer.println("<BR><BR>");
                writer.println("The migration process is complete.");
                if (completeSuccess) {
                    writer.println("There were no errors encountered during the " + "migration process.");
                } else {
                    writer.println("One or more errors were encountered during the " + "migration.");
                    writer.println("Check the above messages for details about the " + "failure(s) that occurred.");
                }

                writer.println("<!-- END MAIN BODY -->" + EOL + EOL);
                writer.println(generateHTMLFooter(requestInfo));
                writer.flush();
                return;
            }
        }

        // Display a form to the user that can be used to select which data should
        // be migrated from the specified directory server.
        htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER
                + "\">Migrate Data from Configuration Directory</SPAN>" + EOL);
        htmlBody.append("<BR><BR>" + EOL);

        htmlBody.append("Please select the data to migrate from the directory " + "server:" + EOL);
        htmlBody.append("<BR><BR>" + EOL);
        htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                + servletBaseURI + "\">" + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append("  "
                + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_MIGRATE) + EOL);
        htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SERVER_INFO_SUBMITTED, "1") + EOL);
        for (int i = 0; i < dsParams.length; i++) {
            htmlBody.append("  " + dsParams[i].generateHidden(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX) + EOL);
        }

        if (requestInfo.debugHTML) {
            htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }

        htmlBody.append("  Folder(s) to Migrate:" + EOL);
        htmlBody.append("  <SELECT NAME=\"" + Constants.SERVLET_PARAM_MIGRATE_FOLDERS + "\">" + EOL);
        htmlBody.append("    <OPTION VALUE=\"" + Constants.MIGRATE_FOLDERS_SELECTED + "\">"
                + Constants.MIGRATE_FOLDERS_SELECTED + EOL);
        htmlBody.append("    <OPTION VALUE=\"" + Constants.MIGRATE_FOLDERS_ALL + "\">"
                + Constants.MIGRATE_FOLDERS_ALL + EOL);
        htmlBody.append("  </SELECT>" + EOL);
        htmlBody.append("  <BR>");

        for (int i = 0; i < folderNames.length; i++) {
            htmlBody.append("  <INPUT TYPE=\"CHECKBOX\" NAME=\"" + Constants.SERVLET_PARAM_JOB_FOLDER
                    + "\" VALUE=\"" + folderNames[i] + "\">" + folderNames[i] + "<BR>" + EOL);
        }

        htmlBody.append("  <BR><BR>" + EOL);
        htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Start Migration\">" + EOL);
        htmlBody.append("</FORM>" + EOL);
    }

    /**
     * Handles all processing required to allow the user to view the set of job
     * classes that have been defined in the SLAMD server, or to view detailed
     * information about a particular job class.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleViewJobClass(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleViewJobClass()");

        // The user must have at least view job class permission to access anything
        // in this section.
        if (!requestInfo.mayViewJobClass) {
            logMessage(requestInfo, "No mayViewJobClass permission granted");
            generateAccessDeniedBody(requestInfo, "You do not have permission to " + "view job class information");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;

        // If a job class name has been specified, then view information for that
        // class.  Otherwise, let the user choose which class to view
        String jobClassName = request.getParameter(Constants.SERVLET_PARAM_JOB_CLASS);
        if ((jobClassName != null) && (jobClassName.length() > 0)) {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Job Class <CODE>" + jobClassName
                    + "</CODE></SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);

            JobClass jobClass = slamdServer.getJobClass(jobClassName);
            if (jobClass == null) {
                htmlBody.append("Job class " + jobClassName + " is not defined in the SLAMD server" + EOL);
                return;
            } else {
                if (requestInfo.mayDeleteJobClass) {
                    htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
                    htmlBody.append("  "
                            + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                            Constants.SERVLET_SECTION_JOB_DELETE_CLASS) + EOL);
                    htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_CLASS, jobClassName) + EOL);
                    if (requestInfo.debugHTML) {
                        htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
                    }
                    htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Delete\">" + EOL);
                    htmlBody.append("</FORM>" + EOL);
                }

                htmlBody.append("<BR>" + EOL);
                htmlBody.append("<B>Job Class Information</B><BR>" + EOL);

                htmlBody.append("<TABLE WIDTH=\"100%\" CELLSPACING=\"0\" " + "BORDER=\"0\">" + EOL);
                htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_A + "\">" + EOL);
                htmlBody.append("    <TD>Class Name</TD>" + EOL);
                htmlBody.append("    <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("    <TD>" + jobClassName + "</TD>" + EOL);
                htmlBody.append("  </TR>" + EOL);
                htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_B + "\">" + EOL);
                htmlBody.append("    <TD>Job Name</TD>" + EOL);
                htmlBody.append("    <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("    <TD>" + jobClass.getJobName() + "</TD>" + EOL);
                htmlBody.append("  </TR>" + EOL);
                htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_A + "\">" + EOL);
                htmlBody.append("    <TD>Description</TD>" + EOL);
                htmlBody.append("    <TD>&nbsp;</TD>" + EOL);
                htmlBody.append("    <TD>" + EOL);
                for (int i = 0; i < jobClass.getLongDescription().length; i++) {
                    if (i > 0) {
                        htmlBody.append("      <BR>" + EOL);
                    }
                    htmlBody.append("      " + jobClass.getLongDescription()[i] + EOL);

                }
                htmlBody.append("    </TD>" + EOL);
                htmlBody.append("  </TR>" + EOL);
                htmlBody.append("</TABLE>" + EOL);

                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("<B>Parameter Information</B>" + EOL);
                htmlBody.append("<TABLE WIDTH=\"100%\" CELLSPACING=\"0\" " + "BORDER=\"0\">" + EOL);

                Parameter[] params = jobClass.getParameterStubs().clone().getParameters();
                for (int i = 0; ((params != null) && (i < params.length)); i++) {
                    if (i % 2 == 0) {
                        htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_A + "\">" + EOL);
                    } else {
                        htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_B + "\">" + EOL);
                    }
                    htmlBody.append("    <TD>" + params[i].getDisplayName() + "</TD>" + EOL);
                    htmlBody.append("    <TD>&nbsp;</TD>" + EOL);

                    String description = replaceText(params[i].getDescription(), "\r\n", "<BR>");
                    description = replaceText(description, "\n", "<BR>");
                    description = replaceText(description, "\t",
                            "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + "&nbsp;&nbsp;");
                    htmlBody.append("    <TD>" + description + "</TD>" + EOL);
                    htmlBody.append("  </TR>" + EOL);
                }

                htmlBody.append("</TABLE>" + EOL);
            }

            StatTracker[] statTrackerStubs = jobClass.getStatTrackerStubs("", "", 1);
            if ((statTrackerStubs != null) && (statTrackerStubs.length > 0)) {
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("<B>Statistics Collected</B>" + EOL);
                htmlBody.append("<TABLE WIDTH=\"100%\" CELLSPACING=\"0\" " + "BORDER=\"0\">" + EOL);
                for (int i = 0; i < statTrackerStubs.length; i++) {
                    if (i % 2 == 0) {
                        htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_A + "\">" + EOL);
                    } else {
                        htmlBody.append("        <TR CLASS=\"" + Constants.STYLE_JOB_SUMMARY_LINE_B + "\">" + EOL);
                    }
                    htmlBody.append("    <TD>" + statTrackerStubs[i].getDisplayName() + "</TD>" + EOL);
                    htmlBody.append("  </TR>" + EOL);
                }

                htmlBody.append("</TABLE>" + EOL);
            }
        } else {
            htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Defined Job Classes</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);

            JobClass[][] categorizedClasses = slamdServer.getCategorizedJobClasses();
            if ((categorizedClasses == null) || (categorizedClasses.length == 0)) {
                htmlBody.append("There are currently no job classes defined in the " + "SLAMD server");
            } else {
                htmlBody.append("The following job classes have been defined in the " + "SLAMD server:" + EOL);
                htmlBody.append("<BR><BR>" + EOL);

                for (int i = 0; i < categorizedClasses.length; i++) {
                    String categoryName = categorizedClasses[i][0].getJobCategoryName();
                    if (categoryName == null) {
                        categoryName = "Unclassified";
                    }

                    htmlBody.append("<B>" + categoryName + " Job Classes</B><BR>" + EOL);
                    htmlBody.append("<UL>" + EOL);

                    for (int j = 0; j < categorizedClasses[i].length; j++) {
                        String link = generateLink(requestInfo, Constants.SERVLET_SECTION_JOB,
                                Constants.SERVLET_SECTION_JOB_VIEW_CLASSES, Constants.SERVLET_PARAM_JOB_CLASS,
                                categorizedClasses[i][j].getClass().getName(),
                                categorizedClasses[i][j].getJobName());
                        htmlBody.append("  <LI>" + link + " (" + categorizedClasses[i][j].getClass().getName()
                                + ")</LI>" + EOL);
                    }

                    htmlBody.append("</UL>" + EOL);
                    htmlBody.append("<BR>" + EOL);
                }
            }
        }
    }

    /**
     * Handles the work required to add a new job class definition to the SLAMD
     * server.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleAddJobClass(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleAddJobClass()");

        // The user must have the add job class permission to see anything here
        if (!requestInfo.mayAddJobClass) {
            logMessage(requestInfo, "No mayAddJobClass permission granted");
            generateAccessDeniedBody(requestInfo,
                    "You do not have permission to " + "define new job classes in the SLAMD server.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // See if a job class name has been specified.  If so, then try to add it
        // to the server.  If not, then let the user specify the class name.
        String jobClassName = request.getParameter(Constants.SERVLET_PARAM_JOB_CLASS);
        if ((jobClassName != null) && (jobClassName.length() > 0)) {
            try {
                JobClass jobClass = slamdServer.loadJobClass(jobClassName);
                if (jobClass instanceof UnknownJobClass) {
                    throw new SLAMDException("Unknown, missing, or invalid job class " + jobClassName);
                }

                slamdServer.addJobClass(jobClass);
                infoMessage
                        .append("Successfully added the job class definition " + "to the SLAMD server.<BR>" + EOL);
                handleViewJobClass(requestInfo);
                return;
            } catch (SLAMDException se) {
                infoMessage.append(se.getMessage() + "<BR>" + EOL);
                requestInfo.response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, se.getMessage());
            }
        }

        htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Define a New Job Class</SPAN>" + EOL);
        htmlBody.append("<BR><BR>" + EOL);
        htmlBody.append("Enter the fully-qualified name of the job class to " + "define in the server." + EOL);
        htmlBody.append("<BR><BR>" + EOL);
        htmlBody.append("<FORM CLASS=\"" + Constants.STYLE_MAIN_FORM + "\" METHOD=\"POST\" ACTION=\""
                + servletBaseURI + "\">" + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
        htmlBody.append(
                "  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_ADD_CLASS)
                        + EOL);
        if (requestInfo.debugHTML) {
            htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
        }
        htmlBody.append("  Job Class Name:  " + EOL);
        htmlBody.append(
                "  <INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_JOB_CLASS + "\" SIZE=\"40\">" + EOL);
        htmlBody.append("<BR><BR>" + EOL);
        htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Add Job Class\">" + EOL);
        htmlBody.append("</FORM>" + EOL);
    }

    /**
     * Handles the work required to install an uploaded job pack.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleInstallJobPack(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleInstallJobPack()");

        // The user must have the add job class permission to see anything here
        if (!requestInfo.mayAddJobClass) {
            logMessage(requestInfo, "No mayAddJobClass permission granted");
            generateAccessDeniedBody(requestInfo,
                    "You do not have permission to " + "define new job classes in the SLAMD server.");
            return;
        }

        // Get the important variables used in this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        String filePath = request.getParameter(Constants.SERVLET_PARAM_JOB_PACK_PATH);

        // Determine whether the request contains uploaded file data.
        if (FileUpload.isMultipartContent(request)) {
            // This request contains the file data, so process it.
            JobPack jobPack = new JobPack(requestInfo);
            try {
                jobPack.processJobPack();
                infoMessage.append("Successfully installed the job pack.<BR>" + EOL);
            } catch (SLAMDServerException sse) {
                infoMessage.append("Error installing job pack -- " + sse.getMessage() + ".<BR>" + EOL);
            }
        } else if ((filePath != null) && (filePath.length() > 0)) {
            // This request contains the path to the job pack on the server's
            // filesystem, so process it.
            JobPack jobPack = new JobPack(requestInfo, filePath);
            try {
                jobPack.processJobPack();
                infoMessage.append("Successfully installed the job pack.<BR>" + EOL);
            } catch (SLAMDServerException sse) {
                requestInfo.response.addHeader(Constants.RESPONSE_HEADER_ERROR_MESSAGE, sse.getMessage());
                infoMessage.append("Error installing job pack -- " + sse.getMessage() + ".<BR>" + EOL);
            }
        } else {
            // The request does not contain the file data, so present a form to allow
            // the user to upload it.
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Install a New Job Pack</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);

            htmlBody.append("Install a job pack file uploaded through the browser." + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM METHOD=\"POST\" ENCTYPE=\"multipart/form-data\" " + "ACTION=\"" + servletBaseURI
                    + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_INSTALL_JOB_PACK) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  Job Pack File:  " + EOL);
            htmlBody.append("  <INPUT TYPE=\"FILE\" NAME=\"" + Constants.SERVLET_PARAM_JOB_PACK_FILE + "\">" + EOL);
            htmlBody.append("  <BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Install Job Pack\">" + EOL);
            htmlBody.append("</FORM>" + EOL);

            htmlBody.append("<BR><BR><HR><BR>" + EOL);
            htmlBody.append("Install a job pack file on the server's filesystem." + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_SUBSECTION,
                    Constants.SERVLET_SECTION_JOB_INSTALL_JOB_PACK) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }

            htmlBody.append("  Job Pack File Path:  " + EOL);
            htmlBody.append("  <INPUT TYPE=\"TEXT\" NAME=\"" + Constants.SERVLET_PARAM_JOB_PACK_PATH
                    + "\" SIZE=\"40\">" + EOL);
            htmlBody.append("  <BR>" + EOL);
            htmlBody.append("  <INPUT TYPE=\"SUBMIT\" VALUE=\"Install Job Pack\">" + EOL);
            htmlBody.append("</FORM>" + EOL);
        }
    }

    /**
     * Handles the work required to remove a job class definition from the SLAMD
     * server, including obtaining confirmation that the user really wants to
     * remove the definition.
     *
     * @param  requestInfo  The state information for this request.
     */
    static void handleDeleteJobClass(RequestInfo requestInfo) {
        logMessage(requestInfo, "In handleDeleteJobClass()");

        // The user must have the delete job class permission to see anything here
        if (!requestInfo.mayDeleteJobClass) {
            logMessage(requestInfo, "No mayDeleteJobClass permission granted");
            generateAccessDeniedBody(requestInfo,
                    "You do not have permission to " + "remove job class definitions from the SLAMD " + "server.");
            return;
        }

        // Get the important state variables for this request.
        HttpServletRequest request = requestInfo.request;
        String servletBaseURI = requestInfo.servletBaseURI;
        StringBuilder htmlBody = requestInfo.htmlBody;
        StringBuilder infoMessage = requestInfo.infoMessage;

        // See if a class name was specified.  If not, then just display an
        // error message.
        String jobClassName = request.getParameter(Constants.SERVLET_PARAM_JOB_CLASS);
        if ((jobClassName == null) || (jobClassName.length() == 0)) {
            infoMessage.append("ERROR:  No job class name specified.<BR>" + EOL);
            return;
        }

        // Make sure that it is a class that is defined in the SLAMD server.
        JobClass jobClass = slamdServer.getJobClass(jobClassName);
        if (jobClass == null) {
            infoMessage
                    .append("ERROR:  Job class " + jobClassName + "is not defined in the SLAMD server.<BR>" + EOL);
            return;
        }

        // If confirmation has not been provided, then request it.  If it has been
        // provided, then remove the job class definition.
        String confirmStr = request.getParameter(Constants.SERVLET_PARAM_CONFIRMED);
        if ((confirmStr == null)
                || ((!confirmStr.equalsIgnoreCase("yes")) && (!confirmStr.equalsIgnoreCase("no")))) {
            htmlBody.append(
                    "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete Job Class Definition</SPAN>" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("Are you sure you want to remove job class <CODE>" + jobClassName
                    + "</CODE> from the SLAMD server?" + EOL);
            htmlBody.append("<BR><BR>" + EOL);
            htmlBody.append("<FORM METHOD=\"POST\" ACTION=\"" + servletBaseURI + "\">" + EOL);
            htmlBody.append(
                    "  " + generateHidden(Constants.SERVLET_PARAM_SECTION, Constants.SERVLET_SECTION_JOB) + EOL);
            htmlBody.append("  "
                    + generateHidden(Constants.SERVLET_PARAM_SUBSECTION, Constants.SERVLET_SECTION_JOB_DELETE_CLASS)
                    + EOL);
            htmlBody.append("  " + generateHidden(Constants.SERVLET_PARAM_JOB_CLASS, jobClassName) + EOL);
            if (requestInfo.debugHTML) {
                htmlBody.append(generateHidden(Constants.SERVLET_PARAM_HTML_DEBUG, "1") + EOL);
            }
            htmlBody.append("  <TABLE BORDER=\"0\" CELLPADDING=\"20\">" + EOL);
            htmlBody.append("    <TR>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"Yes\"></TD>" + EOL);
            htmlBody.append("      <TD><INPUT TYPE=\"SUBMIT\" NAME=\"" + Constants.SERVLET_PARAM_CONFIRMED
                    + "\" VALUE=\"No\"></TD>" + EOL);
            htmlBody.append("    </TR>" + EOL);
            htmlBody.append("  </TABLE>" + EOL);
            htmlBody.append("</FORM>" + EOL);
        } else if (confirmStr.equalsIgnoreCase("yes")) {
            try {
                slamdServer.removeJobClass(jobClass);
                htmlBody.append(
                        "<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete Successful</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("The definition for job class " + jobClassName
                        + " was successfully removed from the SLAMD server." + EOL);
            } catch (SLAMDServerException sse) {
                infoMessage.append("ERROR:  Could not remove job class " + jobClassName + " -- " + sse.getMessage()
                        + "<BR>" + EOL);
                htmlBody.append("<SPAN CLASS=\"" + Constants.STYLE_MAIN_HEADER + "\">Delete Failed</SPAN>" + EOL);
                htmlBody.append("<BR><BR>" + EOL);
                htmlBody.append("The definition for job class " + jobClassName
                        + " could not be removed from the SLAMD server." + EOL);
                htmlBody.append("See the above error message for additional " + "information" + EOL);
            }
        } else {
            infoMessage.append("Job Class " + jobClassName + " was not removed from the SLAMD server.<BR>" + EOL);
            handleViewJobClass(requestInfo);
        }
    }

    /**
     * Generates page content based on an MD5-digest of the query string.
     *
     * @param  requestInfo   The state information for this request.
     * @param  digestString  The base64-encoded MD5 digest of the query string to
     *                       use to generate the page.
     */
    static void generatePageFromMD5(RequestInfo requestInfo, String digestString) {
        try {
            String dataFile = Constants.MD5_CONTENT_BASE_PATH + '/' + digestString;
            InputStream inputStream = slamdServer.getClass().getClassLoader().getResourceAsStream(dataFile);

            byte[] salt = { 0, 0, 0, 0, 0, 0, 0, 0 };
            char[] queryChars = requestInfo.request.getQueryString().toCharArray();
            int iterations = 1000;
            String cipherName = "PBEWithMD5AndDES";
            StringBuilder htmlBody = requestInfo.htmlBody;

            AlgorithmParameters algorithmParams = AlgorithmParameters.getInstance(cipherName);
            algorithmParams.init(new PBEParameterSpec(salt, iterations));
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(cipherName);
            SecretKey key = keyFactory.generateSecret(new PBEKeySpec(queryChars));
            Cipher cipher = Cipher.getInstance(cipherName);
            cipher.init(Cipher.DECRYPT_MODE, key, algorithmParams);

            int bytesIn;
            int bytesOut;
            byte[] inBuffer = new byte[4096];
            byte[] outBuffer = new byte[8192];
            while ((bytesIn = inputStream.read(inBuffer)) > 0) {
                bytesOut = cipher.update(inBuffer, 0, bytesIn, outBuffer);
                htmlBody.append(new String(outBuffer, 0, bytesOut));
            }

            htmlBody.append(new String(cipher.doFinal()));
            inputStream.close();
        } catch (Exception e) {
            requestInfo.htmlBody.append(JobClass.stackTraceToString(e));
        }
    }

    /**
     * Generates the HTML header that will be used for almost all pages generated
     * by this administrative interface.  It will include the header text at the
     * top and the navigation sidebar on the left.
     *
     * @param  requestInfo  The state information for this request.
     *
     * @return  The generated HTML header.
     */
    static String generateHTMLHeader(RequestInfo requestInfo) {
        StringBuilder html = new StringBuilder();

        // Generate the DTD header.  All HTML generated should be compliant to the
        // HTML 4.01 specification.
        html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 " + "Transitional//EN\">" + EOL);
        html.append(EOL);

        html.append("<HTML>" + EOL);
        html.append("  <HEAD>" + EOL);
        html.append("    <TITLE>" + generatePageTitle(requestInfo) + "</TITLE>" + EOL);
        html.append("    <META HTTP-EQUIV=\"Content-Type\" " + "CONTENT=\"text/html; charset=utf-8\">" + EOL);
        html.append(getStyleSheet() + EOL);
        html.append(getAddedHeaderLines() + EOL);
        html.append("  </HEAD>");
        html.append(EOL);

        html.append("  <BODY>" + EOL);

        html.append(EOL);
        html.append("<!-- requestID " + requestInfo.requestID + " -->" + EOL);
        html.append("<!-- section " + requestInfo.section + " -->" + EOL);
        html.append("<!-- subsection " + requestInfo.subsection + " -->" + EOL);
        html.append(debugRequestInfo(requestInfo));
        if (requestInfo.debugHTML) {
            html.append(requestInfo.debugInfo.toString());
        }
        html.append(EOL);

        html.append("    <TABLE BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"5\">" + EOL);
        html.append("      <TR>" + EOL);
        html.append("        <TD WIDTH=\"15%\" VALIGN=\"TOP\">" + EOL);
        html.append("<!-- START SIDE NAVIGATION BAR -->" + EOL);
        html.append(generateSideBar(requestInfo));
        html.append("<!-- END SIDE NAVIGATION BAR -->" + EOL);
        html.append("        </TD>" + EOL);

        html.append(EOL);

        html.append("        <TD WIDTH=\"85%\" VALIGN=\"TOP\">" + EOL);
        html.append("<!-- START PAGE HEADER -->" + EOL);
        html.append(getHeader(requestInfo));
        html.append("<!-- END PAGE HEADER -->" + EOL);
        html.append(EOL);

        return html.toString();
    }

    /**
     * Generates the HTML header that will be used for special content that should
     * not include either the header text or the navigation sidebar (e.g., help
     * pages).
     *
     * @param  requestInfo  The state information for this request.
     *
     * @return  The generated HTML header.
     */
    static String generateHTMLHeaderWithoutSidebar(RequestInfo requestInfo) {
        StringBuilder html = new StringBuilder();

        // Generate the DTD header.  All HTML generated should be compliant to the
        // HTML 4.01 specification.
        html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 " + "Transitional//EN\">" + EOL);
        html.append(EOL);

        html.append("<HTML>" + EOL);
        html.append("  <HEAD>" + EOL);
        html.append("    <TITLE>" + generatePageTitle(requestInfo) + "</TITLE>" + EOL);
        html.append("    <META HTTP-EQUIV=\"Content-Type\" " + "CONTENT=\"text/html; charset=utf-8\">" + EOL);
        html.append(getStyleSheet() + EOL);
        html.append(getAddedHeaderLines() + EOL);
        html.append("  </HEAD>");
        html.append(EOL);

        html.append(debugRequestInfo(requestInfo));
        if (requestInfo.debugHTML) {
            html.append(requestInfo.debugInfo.toString());
        }

        html.append("  <BODY>" + EOL);

        html.append("    <TABLE BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"5\">" + EOL);
        html.append("      <TR>" + EOL);
        html.append("        <TD WIDTH=\"100%\" VALIGN=\"TOP\">" + EOL);

        return html.toString();
    }

    /**
     * Retrieves the style sheet data that should be used for the servlet.
     *
     * @return  The style sheet data that should be used for the servlet.
     */
    static String getStyleSheet() {
        if (styleSheet == null) {
            if (configDB == null) {
                styleSheet = Constants.STYLE_SHEET_DATA;
            } else {
                styleSheet = configDB.getConfigParameter(Constants.PARAM_STYLE_SHEET);
                if ((styleSheet == null) || (styleSheet.length() == 0)) {
                    styleSheet = Constants.STYLE_SHEET_DATA;
                }
            }
        }

        return styleSheet;
    }

    /**
     * Retrieves the set of lines that should be added to the HTML header.
     *
     * @return  The set of lines that should be added to the HTML header.
     */
    static String getAddedHeaderLines() {
        if (addedHeaderLines == null) {
            if (configDB == null) {
                addedHeaderLines = "";
            } else {
                addedHeaderLines = configDB.getConfigParameter(Constants.PARAM_ADD_TO_HTML_HEADER);
                if ((addedHeaderLines == null) || (addedHeaderLines.length() == 0)) {
                    addedHeaderLines = "";
                }
            }
        }

        return addedHeaderLines;
    }

    /**
     * Retrieves the header that should be used at the top of generated pages.
     *
     * @param  requestInfo  The state information for this request.
     *
     * @return  The header that should be used at the top of generated pages.
     */
    static String getHeader(RequestInfo requestInfo) {
        if (pageHeader == null) {
            if (configDB == null) {
                pageHeader = parseHeader(requestInfo, Constants.DEFAULT_PAGE_HEADER);
            } else {
                String headerText = configDB.getConfigParameter(Constants.PARAM_PAGE_HEADER);
                if ((headerText == null) || (headerText.length() == 0)) {
                    headerText = Constants.DEFAULT_PAGE_HEADER;
                }

                pageHeader = parseHeader(requestInfo, headerText);
            }
        }

        return pageHeader;
    }

    /**
     * Parses the provided header text to replace embedded tags with the
     * appropriate values.
     *
     * @param  requestInfo  The state information for this request.
     * @param  headerText   The header text to be parsed.
     *
     * @return  The parsed header text.
     */
    static String parseHeader(RequestInfo requestInfo, String headerText) {
        int pos;
        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_LOGO)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_LOGO.length());

            headerText = preText + "<IMG SRC=\"" + requestInfo.servletBaseURI + '?'
                    + Constants.SERVLET_PARAM_SECTION + '=' + Constants.SERVLET_SECTION_SLAMD_LOGO
                    + "\" ALT=\"SLAMD Logo\">" + postText;
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_VERSION)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_VERSION.length());

            headerText = preText + "Version " + DynamicConstants.SLAMD_VERSION + postText;
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_UNOFFICIAL_BUILD_ID)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_UNOFFICIAL_BUILD_ID.length());

            if (DynamicConstants.OFFICIAL_BUILD) {
                headerText = preText + postText;
            } else {
                headerText = preText + "Unofficial Build " + DynamicConstants.BUILD_DATE + postText;
            }
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_UNOFFICIAL_BUILD)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_UNOFFICIAL_BUILD.length());

            if (DynamicConstants.OFFICIAL_BUILD) {
                headerText = preText + postText;
            } else {
                headerText = preText + "Unofficial Build" + postText;
            }
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_BUILD_DATE)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_BUILD_DATE.length());

            headerText = preText + DynamicConstants.BUILD_DATE + postText;
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_MAJOR_VERSION)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_MAJOR_VERSION.length());

            headerText = preText + DynamicConstants.MAJOR_VERSION + postText;
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_MINOR_VERSION)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_MINOR_VERSION.length());

            headerText = preText + DynamicConstants.MINOR_VERSION + postText;
        }

        while ((pos = headerText.indexOf(Constants.HEADER_TAG_SLAMD_POINT_VERSION)) >= 0) {
            String preText = headerText.substring(0, pos);
            String postText = headerText.substring(pos + Constants.HEADER_TAG_SLAMD_POINT_VERSION.length());

            headerText = preText + DynamicConstants.POINT_VERSION + postText;
        }

        return headerText;
    }

    /**
     * Generates an appropriate title for the page being displayed.  The
     * determination will be based on state information contained in the provided
     * request.
     *
     * @param  requestInfo  The state information for this request.
     *
     * @return  The page title that should be used for the current page.
     */
    static String generatePageTitle(RequestInfo requestInfo) {
        String pageTitle = "SLAMD Distributed Load Generation Engine";

        String section = requestInfo.section;
        String subsection = requestInfo.subsection;

        if (section.equals(Constants.SERVLET_SECTION_CONFIG)) {
            if (subsection.equals(Constants.SERVLET_SECTION_CONFIG_SERVLET)) {
                pageTitle = "Manage Initialization Parameters - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_CONFIG_ACCESS)) {
                pageTitle = "Manage Access Control - " + pageTitle;
            } else {
                pageTitle = "Manage SLAMD Configuration - " + pageTitle;
            }
        } else if (section.equals(Constants.SERVLET_SECTION_STATUS)) {
            pageTitle = "SLAMD Server Status - " + pageTitle;
        } else if (section.equals(Constants.SERVLET_SECTION_JOB)) {
            if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_PENDING)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_RUNNING)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_COMPLETED)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GENERIC)) {
                pageTitle = "View Job Information - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SEARCH)) {
                pageTitle = "Search for Job Information - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_REAL)) {
                pageTitle = "Manage Real Job Folders - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_VIRTUAL)) {
                pageTitle = "Manage Virtual Job Folders - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_FOLDER_DESCRIPTION)) {
                pageTitle = "Edit Folder Description - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_STATS)) {
                pageTitle = "View Job Statistics - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SAVE_STATS)) {
                pageTitle = "Save Job Statistics - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_GRAPH)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_GRAPH)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_OVERLAY)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_OVERLAY)) {
                pageTitle = "Graph Job Statistics - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SCHEDULE)) {
                pageTitle = "Schedule a New Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CLONE)) {
                pageTitle = "Clone a Scheduled Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT)) {
                pageTitle = "Edit a Scheduled Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_EDIT_COMMENTS)) {
                pageTitle = "Edit Job Comments - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CANCEL)) {
                pageTitle = "Cancel a Scheduled Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CANCEL_AND_DELETE)) {
                pageTitle = "Cancel and Delete a Scheduled Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DELETE)) {
                pageTitle = "Delete Job Information - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DISABLE)) {
                pageTitle = "Disable a Scheduled Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ENABLE)) {
                pageTitle = "Enable a Scheduled Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_MASS_OP)
                    || subsection.equals(Constants.SERVLET_SECTION_JOB_MASS_OPTIMIZING)) {
                String submitStr = requestInfo.request.getParameter(Constants.SERVLET_PARAM_SUBMIT);
                if (submitStr.equals(Constants.SUBMIT_STRING_ADD_TO_VIRTUAL_FOLDER)) {
                    pageTitle = "Add Jobs to a Virtual Job Folder - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_CANCEL)) {
                    pageTitle = "Cancel Scheduled Jobs - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_CANCEL_AND_DELETE)) {
                    pageTitle = "Cancel and Delete Scheduled Jobs - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_CLONE)) {
                    pageTitle = "Clone Scheduled Jobs Information - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_COMPARE)) {
                    pageTitle = "Compare Scheduled Jobs - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_CREATE_FOLDER)) {
                    pageTitle = "Create a New Job Folder - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_DELETE)) {
                    pageTitle = "Delete Scheduled Jobs - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_DELETE_FOLDER)) {
                    pageTitle = "Delete a Job Folder - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_DISABLE)) {
                    pageTitle = "Disable Scheduled Jobs - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_ENABLE)) {
                    pageTitle = "Enable Scheduled Jobs - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_EXPORT)) {
                    pageTitle = "Export Job Information - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_MOVE)) {
                    pageTitle = "Move Job Information - " + pageTitle;
                } else if (submitStr.equals(Constants.SUBMIT_STRING_REMOVE_FROM_VIRTUAL_FOLDER)) {
                    pageTitle = "Remove Jobs from a Virtual Job Folder - " + pageTitle;
                }
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_OPTIMIZE)) {
                pageTitle = "Optimize Job Results - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_OPTIMIZE_HELP)) {
                pageTitle = "Optimize Job Results Help - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_OPTIMIZING)) {
                pageTitle = "View Optimizing Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CANCEL_OPTIMIZING)) {
                pageTitle = "Cancel Optimizing Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_CLONE_OPTIMIZING)) {
                pageTitle = "Clone Optimizing Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DELETE_OPTIMIZING)) {
                pageTitle = "Delete Optimizing Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_MOVE_OPTIMIZING)) {
                pageTitle = "Move Optimizing Job - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_VIEW_CLASSES)) {
                pageTitle = "Defined Job Classes - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_ADD_CLASS)) {
                pageTitle = "Define a New Job Class - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_DELETE_CLASS)) {
                pageTitle = "Remove a Defined Job Class - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_INSTALL_JOB_PACK)) {
                pageTitle = "Install a Job Pack - " + pageTitle;
            } else if (subsection.equals(Constants.SERVLET_SECTION_JOB_SCHEDULE_HELP)) {
                pageTitle = "Help on Job Parameters - " + pageTitle;
            } else {
                pageTitle = "SLAMD Job Information - " + pageTitle;
            }
        }

        if (includeAddressInPageTitle) {
            pageTitle = requestInfo.request.getServerName() + " - " + pageTitle;
        }
        return pageTitle;
    }

    /**
     * Replaces all occurrences of a given substring with another substring in the
     * provided string.
     *
     * @param  s        The string in which to perform the replace.
     * @param  find     The substring to find in the provided string.
     * @param  replace  The text to use in place of the specified substring.
     *
     * @return  The updated string.
     */
    static String replaceText(String s, String find, String replace) {
        int pos = s.indexOf(find);
        while (pos >= 0) {
            s = s.substring(0, pos) + replace + s.substring(pos + find.length());
            pos = s.indexOf(find, pos + replace.length());
        }

        return s;
    }

    /**
     * Writes the specified message to the SLAMD server log with the appropriate
     * admin interface log level.  The request ID will be prepended to the
     * message.
     *
     * @param  requestInfo  The state information for this request.
     * @param  message      The message to be written to the SLAMD log file.
     */
    static void logMessage(RequestInfo requestInfo, String message) {
        if (slamdServer != null) {
            slamdServer.logMessage(Constants.LOG_LEVEL_ADMIN, requestInfo.requestID + " - " + message);
        }

        if (requestInfo.debugInfo != null) {
            requestInfo.debugInfo.append("<!-- " + message + " -->" + EOL);
        }
    }

    /**
     * Retrieves the class path for this servlet.
     *
     * @return  The class path for this servlet.
     */
    public static String getClassPath() {
        return classPath;
    }

    /**
     * Retrieves the path to the WEB-INF directory for the admin interface.
     *
     * @return  The path to the WEB-INF directory for the admin interface.
     */
    public static String getWebInfPath() {
        return webInfBasePath;
    }

    /**
     * Converts breaks the provided string into an array of lines.
     *
     * @param  stringValue  The string to be broken into a line array.
     *
     * @return  The string array converted to an array of lines.
     */
    static String[] stringToLineArray(String stringValue) {
        ArrayList<String> lineList = new ArrayList<String>();

        if (stringValue != null) {
            StringTokenizer tokenizer = new StringTokenizer(stringValue, "\r\n");
            while (tokenizer.hasMoreTokens()) {
                lineList.add(tokenizer.nextToken());
            }
        }

        String[] lineArray = new String[lineList.size()];
        lineList.toArray(lineArray);
        return lineArray;
    }
}