/* * FlowUtils.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002, 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.xmlui.aspect.submission; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.http.HttpEnvironment; import org.apache.log4j.Logger; import org.dspace.app.util.DCInput; import org.dspace.app.util.SubmissionConfig; import org.dspace.app.util.SubmissionConfigReader; import org.dspace.app.util.SubmissionInfo; import org.dspace.app.util.SubmissionStepConfig; import org.dspace.app.xmlui.utils.UIException; import org.dspace.app.xmlui.utils.ContextUtil; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.InProgressSubmission; import org.dspace.content.Item; import org.dspace.content.WorkspaceItem; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.handle.HandleManager; import org.dspace.submit.AbstractProcessingStep; import org.dspace.workflow.WorkflowItem; import org.dspace.workflow.WorkflowManager; /** * This is a utility class to aid in the submission flow scripts. * Since data validation is cumbersome inside a flow script this * is a collection of methods to preform processing at each step * of the flow, the flow script will ties these operations * together in a meaningful order but all actually processing * is done through these various processes. * * @author Scott Phillips * @author Tim Donohue (modified for Configurable Submission) */ public class FlowUtils { private static Logger log = Logger.getLogger(FlowUtils.class); /** Where the submissionInfo is stored on an HTTP Request object */ private final static String DSPACE_SUBMISSION_INFO = "dspace.submission.info"; /** * Return the InProgressSubmission, either workspaceItem or workflowItem, * depending on the id provided. If the id begins with an S then it is a * considered a workspaceItem. If the id begins with a W then it is * considered a workflowItem. * * @param context * @param inProgressSubmissionID * @return The InprogressSubmission or null if non found */ public static InProgressSubmission findSubmission(Context context, String inProgressSubmissionID) throws SQLException { char type = inProgressSubmissionID.charAt(0); int id = Integer.valueOf(inProgressSubmissionID.substring(1)); if (type == 'S') { return WorkspaceItem.find(context, id); } else if (type == 'W') { return WorkflowItem.find(context, id); } return null; } /** * Return the workspace identified by the given id, the id should be * prepended with the character S to signify that it is a workspace * instead of a workflow. * * @param context * @param inProgressSubmissionID * @return The found workspaceitem or null if none found. */ public static WorkspaceItem findWorkspace(Context context, String inProgressSubmissionID) throws SQLException { InProgressSubmission submission = findSubmission(context, inProgressSubmissionID); if (submission instanceof WorkspaceItem) return (WorkspaceItem) submission; return null; } /** * Return the workflow identified by the given id, the id should be * prepended with the character S to signify that it is a workflow * instead of a workspace. * * @param context * @param inProgressSubmissionID * @return The found workflowitem or null if none found. */ public static WorkflowItem findWorkflow(Context context, String inProgressSubmissionID) throws SQLException { InProgressSubmission submission = findSubmission(context, inProgressSubmissionID); if (submission instanceof WorkflowItem) return (WorkflowItem) submission; return null; } /** * Obtains the submission info for the current submission process. * If a submissionInfo object has already been created * for this HTTP request, it is re-used, otherwise it is created. * * @param objectModel * the cocoon Objectmodel * @param workspaceID * the workspaceID of the submission info to obtain * * @return a SubmissionInfo object */ public static SubmissionInfo obtainSubmissionInfo(Map objectModel, String workspaceID) throws SQLException { Request request = ObjectModelHelper.getRequest(objectModel); Context context = ContextUtil.obtainContext(objectModel); //try loading subInfo from HTTP request SubmissionInfo subInfo = (SubmissionInfo) request.getAttribute(DSPACE_SUBMISSION_INFO); //get the submission represented by the WorkspaceID InProgressSubmission submission = findSubmission(context, workspaceID); //if no submission info, or wrong submission info, reload it! if ((subInfo == null && submission!=null) || (subInfo!=null && submission!=null && subInfo.getSubmissionItem().getID()!=submission.getID())) { try { final HttpServletRequest httpRequest = (HttpServletRequest) objectModel .get(HttpEnvironment.HTTP_REQUEST_OBJECT); // load submission info subInfo = SubmissionInfo.load(httpRequest, submission); // Set the session ID context.setExtraLogInfo("session_id=" + request.getSession().getId()); // Store the submissionInfo in the request request.setAttribute(DSPACE_SUBMISSION_INFO, subInfo); } catch(Exception e) { throw new SQLException("Error loading Submission Info: " + e.getMessage()); } } else if(subInfo==null && submission==null) { throw new SQLException("Unable to load Submission Information, since WorkspaceID (ID:" + workspaceID + ") is not a valid in-process submission."); } return subInfo; } /** * Indicate the user has advanced to the given page within a given step. * This will only actually do anything when it's a user initially entering * a submission. It will increase the "stage reached" and "page reached" * columns - it will not "set back" where a user has reached. * * @param context The current DSpace content * @param id The unique ID of the current workflow/workspace * @param step the step the user has just reached * @param page the page (within the step) the user has just reached */ public static void setPageReached(Context context, String id, int step, int page) throws SQLException, AuthorizeException, IOException { InProgressSubmission submission = findSubmission(context, id); if (submission instanceof WorkspaceItem) { WorkspaceItem workspaceItem = (WorkspaceItem) submission; if (step > workspaceItem.getStageReached()) { workspaceItem.setStageReached(step); workspaceItem.setPageReached(1); //reset page to first page in new step workspaceItem.update(); context.commit(); } else if ((step == workspaceItem.getStageReached()) && (page > workspaceItem.getPageReached())) { workspaceItem.setPageReached(page); workspaceItem.update(); context.commit(); } } } /** * Set a specific step and page as reached. * It will also "set back" where a user has reached. * * @param context The current DSpace content * @param id The unique ID of the current workflow/workspace * @param step the step to set as reached, can be also a previous reached step * @param page the page (within the step) to set as reached, can be also a previous reached page */ public static void setBackPageReached(Context context, String id, int step, int page) throws SQLException, AuthorizeException, IOException { InProgressSubmission submission = findSubmission(context, id); if (submission instanceof WorkspaceItem) { WorkspaceItem workspaceItem = (WorkspaceItem) submission; workspaceItem.setStageReached(step); workspaceItem.setPageReached(page > 0 ? page : 1); workspaceItem.update(); context.commit(); } } /** * Find the maximum step the user has reached in the submission processes. * If this submission is a workflow then return max-int. * * @param context The current DSpace content * @param id The unique ID of the current workflow/workspace */ public static int getMaximumStepReached(Context context, String id) throws SQLException { InProgressSubmission submission = findSubmission(context, id); if (submission instanceof WorkspaceItem) { WorkspaceItem workspaceItem = (WorkspaceItem) submission; int stage = workspaceItem.getStageReached(); if (stage < 0) stage = 0; return stage; } // This is a workflow, return infinity. return Integer.MAX_VALUE; } /** * Find the maximum page (within the maximum step) that the user has * reached in the submission processes. * If this submission is a workflow then return max-int. * * @param context The current DSpace content * @param id The unique ID of the current workflow/workspace */ public static int getMaximumPageReached(Context context, String id) throws SQLException { InProgressSubmission submission = findSubmission(context, id); if (submission instanceof WorkspaceItem) { WorkspaceItem workspaceItem = (WorkspaceItem) submission; int page = workspaceItem.getPageReached(); if (page < 0) page = 0; return page; } // This is a workflow, return infinity. return Integer.MAX_VALUE; } /** * Get current step number * * @param stepAndPage * a double representing the current step and page * (e.g. 1.2 is page 2 of step 1) * @return step number */ public static int getStep(double stepAndPage) { //split step and page (e.g. 1.2 is page 2 of step 1) String[] fields = Double.toString(stepAndPage).split("\\."); // split on period return Integer.parseInt(fields[0]); } /** * Get number of the current page within the current step * *@param stepAndPage * a double representing the current step and page * (e.g. 1.2 is page 2 of step 1) * @return page number (within current step) */ public static int getPage(double stepAndPage) { //split step and page (e.g. 1.2 is page 2 of step 1) String[] fields = Double.toString(stepAndPage).split("\\."); // split on period return Integer.parseInt(fields[1]); } /** * Process the save or remove step. If the user has selected to * remove their submission then remove it. * * @param context The current DSpace content * @param id The unique ID of the current workspace/workflow * @param request The cocoon request object. */ public static void processSaveOrRemove(Context context, String id, Request request) throws SQLException, AuthorizeException, IOException { if (request.getParameter("submit_remove") != null) { // If they selected to remove the item then delete everything. WorkspaceItem workspace = findWorkspace(context,id); workspace.deleteAll(); context.commit(); } } /** * Update the provided workflowItem to advance to the next workflow * step. If this was the last thing needed before the item is * committed to the repository then return true, otherwise false. * * @param context The current DSpace content * @param id The unique ID of the current workflow */ public static boolean processApproveTask(Context context, String id) throws SQLException, UIException, ServletException, AuthorizeException, IOException { WorkflowItem workflowItem = findWorkflow(context, id); Item item = workflowItem.getItem(); // Advance the item along the workflow WorkflowManager.advance(context, workflowItem, context.getCurrentUser()); // FIXME: This should be a return value from advance() // See if that gave the item a Handle. If it did, // the item made it into the archive, so we // should display a suitable page. String handle = HandleManager.findHandle(context, item); context.commit(); if (handle != null) { return true; } else { return false; } } /** * Return the given task back to the pool of unclaimed tasks for another user * to select and preform. * * @param context The current DSpace content * @param id The unique ID of the current workflow */ public static void processUnclaimTask(Context context, String id) throws SQLException, UIException, ServletException, AuthorizeException, IOException { WorkflowItem workflowItem = findWorkflow(context, id); // Return task to pool WorkflowManager.unclaim(context, workflowItem, context.getCurrentUser()); context.commit(); //Log this unclaim action log.info(LogManager.getHeader(context, "unclaim_workflow", "workflow_item_id=" + workflowItem.getID() + ",item_id=" + workflowItem.getItem().getID() + ",collection_id=" + workflowItem.getCollection().getID() + ",new_state=" + workflowItem.getState())); } /** * Claim this task from the pool of unclaimed task so that this user may * preform the task by either approving or rejecting it. * * @param context The current DSpace content * @param id The unique ID of the current workflow */ public static void processClaimTask(Context context, String id) throws SQLException, UIException, ServletException, AuthorizeException, IOException { WorkflowItem workflowItem = findWorkflow(context, id); // Claim the task WorkflowManager.claim(context, workflowItem, context.getCurrentUser()); context.commit(); //log this claim information log.info(LogManager.getHeader(context, "claim_task", "workflow_item_id=" + workflowItem.getID() + "item_id=" + workflowItem.getItem().getID() + "collection_id=" + workflowItem.getCollection().getID() + "newowner_id=" + workflowItem.getOwner().getID() + "new_state=" + workflowItem.getState())); } /** * Reject the given task for the given reason. If the user did not provide * a reason then an error is generated placing that field in error. * * @param context The current DSpace content * @param id The unique ID of the current workflow * @param request The current request object */ public static String processRejectTask(Context context, String id,Request request) throws SQLException, UIException, ServletException, AuthorizeException, IOException { WorkflowItem workflowItem = findWorkflow(context, id); String reason = request.getParameter("reason"); if (reason != null && reason.length() > 1) { WorkspaceItem wsi = WorkflowManager.reject(context, workflowItem,context.getCurrentUser(), reason); //Load the Submission Process for the collection this WSI is associated with Collection c = wsi.getCollection(); SubmissionConfigReader subConfigReader = new SubmissionConfigReader(); SubmissionConfig subConfig = subConfigReader.getSubmissionConfig(c.getHandle(), false); // Set the "stage_reached" column on the workspace item // to the LAST page of the LAST step in the submission process // (i.e. the page just before "Complete", which is at NumSteps-1) int lastStep = subConfig.getNumberOfSteps()-2; wsi.setStageReached(lastStep); wsi.setPageReached(AbstractProcessingStep.LAST_PAGE_REACHED); wsi.update(); context.commit(); //Submission rejected. Log this information log.info(LogManager.getHeader(context, "reject_workflow", "workflow_item_id=" + wsi.getID() + "item_id=" + wsi.getItem().getID() + "collection_id=" + wsi.getCollection().getID() + "eperson_id=" + context.getCurrentUser().getID())); // Return no errors. return null; } else { // If the user did not supply a reason then // place the reason field in error. return "reason"; } } /** * Return the HTML / DRI field name for the given input. * * @param input * @return field name as a String (e.g. dc_contributor_editor) */ public static String getFieldName(DCInput input) { String dcSchema = input.getSchema(); String dcElement = input.getElement(); String dcQualifier = input.getQualifier(); if (dcQualifier != null && ! dcQualifier.equals(Item.ANY)) { return dcSchema + "_" + dcElement + '_' + dcQualifier; } else { return dcSchema + "_" + dcElement; } } /** * Retrieves a list of all steps and pages within the * current submission process. * <P> * This returns an array of Doubles of the form "#.#" * where the former number is the step number, and the * latter is the page number. * <P> * This list may differ from the list of steps in the * progress bar if the current submission process includes * non-interactive steps which do not appear in the progress bar! * <P> * This method is used by the Manakin submission flowscript * (submission.js) to step forward/backward between steps. * * @param request * The HTTP Servlet Request object * @param subInfo * the current SubmissionInfo object * */ public static Double[] getListOfAllSteps(HttpServletRequest request, SubmissionInfo subInfo) { ArrayList listStepNumbers = new ArrayList(); // loop through all steps for (int i = 0; i < subInfo.getSubmissionConfig().getNumberOfSteps(); i++) { // get the current step info SubmissionStepConfig currentStep = subInfo.getSubmissionConfig() .getStep(i); String stepNumber = Integer.toString(currentStep .getStepNumber()); //Skip over the "Select Collection" step, since // a user is never allowed to return to that step or jump from that step if(currentStep.getId()!=null && currentStep.getId().equals(SubmissionStepConfig.SELECT_COLLECTION_STEP)) { continue; } // default to just one page in this step int numPages = 1; try { // load the processing class for this step ClassLoader loader = subInfo.getClass().getClassLoader(); Class stepClass = loader.loadClass(currentStep.getProcessingClassName()); // call the "getNumberOfPages()" method of the class // to get it's number of pages AbstractProcessingStep step = (AbstractProcessingStep) stepClass .newInstance(); // get number of pages from servlet numPages = step.getNumberOfPages(request, subInfo); } catch (Exception e) { log.error("Error loading step information from Step Class '" + currentStep.getProcessingClassName() + "' Error:", e); } // save each of the step's pages to the progress bar for (int j = 1; j <= numPages; j++) { String stepAndPage = stepNumber + "." + j; Double stepAndPageNum = Double.valueOf(stepAndPage); listStepNumbers.add(stepAndPageNum); }// end for each page }// end for each step //convert into an array of Doubles return (Double[]) listStepNumbers.toArray(new Double[listStepNumbers.size()]); } }