/* * JSPStepManager.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.webui.submit; import java.io.IOException; import java.sql.SQLException; import java.util.Iterator; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.dspace.app.webui.servlet.SubmissionController; import org.dspace.app.webui.util.JSPManager; import org.dspace.app.util.SubmissionInfo; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.authorize.AuthorizeException; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; import org.dspace.submit.AbstractProcessingStep; /** * Manages and processes all JSP-UI classes for DSpace Submission steps. * <P> * This manager is utilized by the SubmissionController to appropriately * load each JSP-UI step, and process any information returned by each step * * @see org.dspace.submit.AbstractProcessingStep * @see org.dspace.app.webui.servlet.SubmissionController * @see org.dspace.app.webui.submit.JSPStep * * @author Tim Donohue * @version $Revision: 3705 $ */ public class JSPStepManager { /** log4j logger */ private static Logger log = Logger.getLogger(JSPStepManager.class); /** * Current Processing class for step that is being processed by the JSPStepManager * This is the class that performs processing of information entered in during a step */ private AbstractProcessingStep stepProcessing = null; /** * Current JSP-UI binding class for step that is being processed by the JSPStepManager * This is the class that manages calling all JSPs, and determines if additional processing * of information (or confirmation) is necessary. */ private JSPStep stepJSPUI = null; /** * The SubmissionStepConfig object describing the current step **/ private SubmissionStepConfig stepConfig = null; /** * Initialize the current JSPStepManager object, by loading the * specified step class. * * @param stepConfig * the SubmissionStepConfig object which describes * this step's configuration in the item-submission.xml * * @throws Exception * if the JSPStep cannot be loaded or the class * specified doesn't implement the JSPStep interface */ public static JSPStepManager loadStep(SubmissionStepConfig stepConfig) throws Exception { JSPStepManager stepManager = new JSPStepManager(); //save step configuration stepManager.stepConfig = stepConfig; /* * First, load the step processing class (using the current class loader) */ ClassLoader loader = stepManager.getClass().getClassLoader(); Class stepClass = loader .loadClass(stepConfig.getProcessingClassName()); Object stepInstance = stepClass.newInstance(); if(stepInstance instanceof AbstractProcessingStep) { // load the JSPStep interface for this step stepManager.stepProcessing = (AbstractProcessingStep) stepClass.newInstance(); } else { throw new Exception("The submission step class specified by '" + stepConfig.getProcessingClassName() + "' does not extend the class org.dspace.submit.AbstractProcessingStep!" + " Therefore it cannot be used by the Configurable Submission as the <processing-class>!"); } /* * Next, load the step's JSPUI binding class (using the current class loader) * (Only load JSPUI binding class if specified...otherwise this is a non-interactive step) */ if(stepConfig.getJSPUIClassName()!=null && stepConfig.getJSPUIClassName().length()>0) { stepClass = loader .loadClass(stepConfig.getJSPUIClassName()); stepInstance = stepClass.newInstance(); if(stepInstance instanceof JSPStep) { // load the JSPStep interface for this step stepManager.stepJSPUI = (JSPStep) stepClass.newInstance(); } else { throw new Exception("The submission step class specified by '" + stepConfig.getJSPUIClassName() + "' does not extend the class org.dspace.app.webui.JSPStep!" + " Therefore it cannot be used by the Configurable Submission for the JSP user interface!"); } } return stepManager; } /** * Initialize the current JSPStepManager object, to prepare for processing * of this step. This method is called by the SubmissionController, and its * job is to determine which "page" of the step the current user is on. * <P> * Once the page has been determined, this method also determines whether * the user is completing this page (i.e. there is user input data that * needs to be saved to the database) or beginning this page (i.e. the user * needs to be sent to the JSP for this page). * * @param context * a DSpace Context object * @param request * the HTTP request * @param response * the HTTP response * @param subInfo * submission info object * * @return true if step is completed (successfully), false otherwise * * @throws ServletException * if a general error occurs * @throws IOException * if a i/o error occurs * @throws SQLException * if a database error occurs * @throws AuthorizeException * if some authorization error occurs */ public final boolean processStep(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { /* * This method SHOULD NOT BE OVERRIDDEN, unless it's absolutely * necessary. If you override this method, make sure you call the * "doStepStart()" and "doStepEnd()" methods at the appropriate time in * your processStep() method. * */ /* * Figure out Current Page in this Step */ int currentPage = -1; // if user came to this Step by pressing "Previous" button // from the following step, then we will have to calculate // the correct page to go to. if ((request.getAttribute("step.backwards") != null) && ((Boolean) request.getAttribute("step.backwards")) .booleanValue()) { // current page should be the LAST page in this step currentPage = getNumPagesInProgressBar(subInfo, this.stepConfig .getStepNumber()); AbstractProcessingStep.setCurrentPage(request, currentPage); } else { // retrieve current page number from request currentPage = AbstractProcessingStep.getCurrentPage(request); } /* * Update Last page reached (if necessary) */ int pageReached = getPageReached(subInfo); // TD: Special Case, where an item was just rejected & returned to // Workspace. In this case, we have an invalid "pageReached" in // the database which needs updating if (pageReached == AbstractProcessingStep.LAST_PAGE_REACHED) { // the first time this flag is encountered, we have to update // the Database with the number of the last page in the current // StepReached // get the number of pages in the last step reached int lastPageNum = getNumPagesInProgressBar(subInfo, SubmissionController.getStepReached(subInfo)); // update database with the number of this last page updatePageReached(subInfo, lastPageNum); context.commit(); } // otherwise, check if pageReached needs updating // (it only needs updating if we're in the latest step reached, // and we've made it to a later page in that step) else if ((this.stepConfig.getStepNumber() == SubmissionController .getStepReached(subInfo)) && (currentPage > pageReached)) { // reset page number reached & commit to database updatePageReached(subInfo, currentPage); context.commit(); } /* * Determine whether we are Starting or Finishing this Step */ // check if we just started this step boolean beginningOfStep = SubmissionController .isBeginningOfStep(request); // if this step has just been started, do beginning processing if (beginningOfStep) { return doStepStart(context, request, response, subInfo); } else // We just returned from a page (i.e. JSP), so we need to do any end // processing { return doStepEnd(context, request, response, subInfo); } } /** * Do the beginning of a Step. First do pre-processing, then display the JSP * (if there is one). If there's no JSP, just End the step immediately. * * @param context * current DSpace context * @param request * current servlet request object * @param response * current servlet response object * @param subInfo * submission info object * * @return true if the step is completed (no JSP was loaded), false * otherwise * */ private boolean doStepStart(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { log.debug("Doing pre-processing for step " + this.getClass().getName()); // first, do any pre-processing and get the JSP to display // (assuming that this step has an interface) if(stepJSPUI!=null) stepJSPUI.doPreProcessing(context, request, response, subInfo); // Complete this step, if this response has not already // been committed. // // Note: the response should only be "committed" if the user // has be forwarded on to a JSP page (in which case the step // is not finished!). if (!response.isCommitted()) { // Otherwise, this is a non-interactive step! // There's no JSP to display, so only perform the processing // and forward back to the Submission Controller servlet log.debug("Calling processing for step " + this.getClass().getName()); int errorFlag = stepProcessing.doProcessing(context, request, response, subInfo); // if it didn't complete successfully, try and log this error! if (errorFlag != AbstractProcessingStep.STATUS_COMPLETE) { // see if an error message was defined! String errorMessage = stepProcessing.getErrorMessage(errorFlag); // if not defined, construct a dummy error if (errorMessage == null) { errorMessage = "The doProcessing() method for " + this.getClass().getName() + " returned an error flag = " + errorFlag + ". " + "It is recommended to define a custom error message for this error flag using the addErrorMessage() method!"; } log.error(errorMessage); } return completeStep(context, request, response, subInfo); } else return false; // step not completed } /** * This method actually displays a JSP page for this "step". This method can * be called from "doPostProcessing()" to display an error page, etc. * * @param request * current servlet request object * @param response * current servlet response object * @param subInfo * submission info object * @param pathToJSP * context path to the JSP to display * */ public static final void showJSP(HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo, String pathToJSP) throws ServletException, IOException, SQLException { // As long as user is not currently cancelling // or saving submission, show the JSP specified if (!response.isCommitted() && !SubmissionController.isCancellationInProgress(request)) { // set beginningOfStep flag to false // (so that we know it will be time for "post-processing" // after loading the JSP) SubmissionController.setBeginningOfStep(request, false); // save our current Submission information into the Request // object (so JSP has access to it) SubmissionController.saveSubmissionInfo(request, subInfo); // save our current page information to Request object //setCurrentPage(request, currentPage); // save the JSP we are displaying setLastJSPDisplayed(request, pathToJSP); // load JSP JSPManager.showJSP(request, response, pathToJSP); } } /** * Do the end of a Step. If "cancel" or "progress bar" was pressed, we need * to forward back to the SubmissionManagerServlet immediately. Otherwise, * do Post-processing, and figure out if this step is truly finished or not. * * @param context * current DSpace context * @param request * current servlet request object * @param response * current servlet response object * @param subInfo * submission info object * @return true if the step is completed (successfully), false otherwise * */ private boolean doStepEnd(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { // we've returned from the JSP page // and need to do the processing for this step log.debug("Calling processing for step " + this.getClass().getName()); int status = stepProcessing.doProcessing(context, request, response, subInfo); log.debug("Calling post-processing for step " + this.getClass().getName()); // After doing the processing, we have to do any post-processing // of any potential error messages, in case we need to re-display // the JSP stepJSPUI.doPostProcessing(context, request, response, subInfo, status); int currentPage = AbstractProcessingStep.getCurrentPage(request); // Assuming step didn't forward back to a JSP during // post-processing, then check to see if this is the last page! if (!response.isCommitted()) { // check if this step has more pages! if (!hasMorePages(request, subInfo, currentPage)) { // this step is complete! (return completion // status to SubmissionController) return completeStep(context, request, response, subInfo); } else { // otherwise, increment to the next page AbstractProcessingStep.setCurrentPage(request, currentPage + 1); // reset to beginning of step (so that pre-processing is run // again) SubmissionController.setBeginningOfStep(request, true); // recursively call processStep to reload this Step class for // the next page return processStep(context, request, response, subInfo); } } else { // this is the case when a response was already given // in doPostProcessing(), which means the user was // forwarded to a JSP page. return false; } } /** * This method completes the processing of this "step" and forwards the * request back to the SubmissionController (so that the next step can be * called). * * @param context * current DSpace context * @param request * current servlet request object * @param response * current servlet response object * @param subInfo * submission info object * * @return true if step completed (successfully), false otherwise */ protected final boolean completeStep(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) throws ServletException, IOException, SQLException, AuthorizeException { log.debug("Completing Step " + this.getClass().getName()); // If a response has not already been sent to the web browser, // then forward control back to the SubmissionController // (and let the controller know this step is complete! // // Note: The only cases in which a response would have already // been sent to the web browser is if an error occurred or the // Submission Process had to immediately exit for some reason // (e.g. user rejected license, or tried to submit a thesis while theses // are blocked, etc.) if (!response.isCommitted()) { // save our current Submission information into the Request object SubmissionController.saveSubmissionInfo(request, subInfo); // save the current step into the Request object SubmissionController.saveCurrentStepConfig(request, stepConfig); // reset current page back to 1 AbstractProcessingStep.setCurrentPage(request, 1); // return success (true) to controller! return true; } else return false; // couldn't return to controller since response is // committed } /** * Checks to see if there are more pages in the current step after the * specified page * * @param request * The HTTP Request object * @param subInfo * The current submission information object * @param pageNumber * The current page * * @throws ServletException * if there are no more pages in this step * */ protected final boolean hasMorePages(HttpServletRequest request, SubmissionInfo subInfo, int pageNumber) throws ServletException { int numberOfPages = stepProcessing.getNumberOfPages(request, subInfo); if (pageNumber < numberOfPages) { return true; } else { return false; } } /** * Find out which page a user has reached in this particular step. * * @param subInfo * Submission information * * @return page reached */ public static final int getPageReached(SubmissionInfo subInfo) { if (subInfo.isInWorkflow() || subInfo.getSubmissionItem() == null) { return -1; } else { WorkspaceItem wi = (WorkspaceItem) subInfo.getSubmissionItem(); int i = wi.getPageReached(); return i; } } /** * Sets the number of the page reached for the specified step * * @param session * HTTP session (where page reached is stored) * @param step * the current Submission Process Step (which we want to * increment the page reached) * @param pageNumber * new page reached */ private void updatePageReached(SubmissionInfo subInfo, int page) throws SQLException, AuthorizeException, IOException { if (!subInfo.isInWorkflow() && subInfo.getSubmissionItem() != null) { WorkspaceItem wi = (WorkspaceItem) subInfo.getSubmissionItem(); if (page > wi.getPageReached()) { wi.setPageReached(page); wi.update(); } } } /** * Return the number pages for the given step in the Submission Process * according to the Progress Bar. So, if "stepNum" is 7, this will return * the number of pages in step #7 (at least according to the progressBar) * * @param subInfo * current Submission Information object * @param stepNum * the number of the step * * @return the number of pages in the step */ private static int getNumPagesInProgressBar(SubmissionInfo subInfo, int stepNum) { // get the keys of the progressBar information (key format: // stepNum.pageNum) Iterator keyIterator = subInfo.getProgressBarInfo().keySet().iterator(); // default to last page being page #1 int lastPage = 1; while (keyIterator.hasNext()) { // find step & page info (format: stepNum.pageNum) String stepAndPage = (String) keyIterator.next(); if (stepAndPage.startsWith(stepNum + ".")) { // split into stepNum and pageNum String[] fields = stepAndPage.split("\\."); // split on period int page = Integer.parseInt(fields[1]); if (page > lastPage) { // update last page found for this step lastPage = page; } } } // return # of last page found for this step (which is also the number // of pages) return lastPage; } /** * Retrieves the context path of the last JSP that was displayed to the * user. This is useful in the "doPostProcessing()" method to determine * which JSP has just submitted form information. * * @param request * current servlet request object * * @returns pathToJSP The context path to the JSP page (e.g. * "/submit/select-collection.jsp") */ public static final String getLastJSPDisplayed(HttpServletRequest request) { String jspDisplayed = (String) request.getAttribute("jsp"); if ((jspDisplayed == null) || jspDisplayed.length() == 0) { // try and retrieve the JSP name as a request parameter if (request.getParameter("jsp") == null) { jspDisplayed = ""; } else { jspDisplayed = request.getParameter("jsp"); } } return jspDisplayed; } /** * Saves the context path of the last JSP that was displayed to the user. * * @param request * current servlet request object * @param pathToJSP * The context path to the JSP page (e.g. * "/submit/select-collection.jsp") */ private static final void setLastJSPDisplayed(HttpServletRequest request, String pathToJSP) { // save to request request.setAttribute("jsp", pathToJSP); } /** * Return the URL path (e.g. /submit/review-metadata.jsp) of the JSP * which will review the information that was gathered in the currently * loaded Step. * <P> * This Review JSP is loaded by the 'Verify' Step, in order to dynamically * generate a submission verification page consisting of the information * gathered in all the enabled submission steps. * * @param context * current DSpace context * @param request * current servlet request object * @param response * current servlet response object * @param subInfo * submission info object */ public String getReviewJSP(Context context, HttpServletRequest request, HttpServletResponse response, SubmissionInfo subInfo) { return stepJSPUI.getReviewJSP(context, request, response, subInfo); } }