001: /*
002: * FlowUtils.java
003: *
004: * Version: $Revision: 1.21 $
005: *
006: * Date: $Date: 2006/07/27 18:24:34 $
007: *
008: * Copyright (c) 2002, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */
040:
041: package org.dspace.app.xmlui.aspect.submission;
042:
043: import java.io.IOException;
044: import java.sql.SQLException;
045: import java.util.ArrayList;
046: import java.util.Map;
047:
048: import javax.servlet.ServletException;
049: import javax.servlet.http.HttpServletRequest;
050:
051: import org.apache.cocoon.environment.ObjectModelHelper;
052: import org.apache.cocoon.environment.Request;
053: import org.apache.cocoon.environment.http.HttpEnvironment;
054: import org.apache.log4j.Logger;
055: import org.dspace.app.util.DCInput;
056: import org.dspace.app.util.SubmissionConfig;
057: import org.dspace.app.util.SubmissionConfigReader;
058: import org.dspace.app.util.SubmissionInfo;
059: import org.dspace.app.util.SubmissionStepConfig;
060: import org.dspace.app.xmlui.utils.UIException;
061: import org.dspace.app.xmlui.utils.ContextUtil;
062: import org.dspace.authorize.AuthorizeException;
063: import org.dspace.content.Collection;
064: import org.dspace.content.InProgressSubmission;
065: import org.dspace.content.Item;
066: import org.dspace.content.WorkspaceItem;
067: import org.dspace.core.Context;
068: import org.dspace.core.LogManager;
069: import org.dspace.handle.HandleManager;
070: import org.dspace.submit.AbstractProcessingStep;
071: import org.dspace.workflow.WorkflowItem;
072: import org.dspace.workflow.WorkflowManager;
073:
074: /**
075: * This is a utility class to aid in the submission flow scripts.
076: * Since data validation is cumbersome inside a flow script this
077: * is a collection of methods to preform processing at each step
078: * of the flow, the flow script will ties these operations
079: * together in a meaningful order but all actually processing
080: * is done through these various processes.
081: *
082: * @author Scott Phillips
083: * @author Tim Donohue (modified for Configurable Submission)
084: */
085:
086: public class FlowUtils {
087:
088: private static Logger log = Logger.getLogger(FlowUtils.class);
089:
090: /** Where the submissionInfo is stored on an HTTP Request object */
091: private final static String DSPACE_SUBMISSION_INFO = "dspace.submission.info";
092:
093: /**
094: * Return the InProgressSubmission, either workspaceItem or workflowItem,
095: * depending on the id provided. If the id begins with an S then it is a
096: * considered a workspaceItem. If the id begins with a W then it is
097: * considered a workflowItem.
098: *
099: * @param context
100: * @param inProgressSubmissionID
101: * @return The InprogressSubmission or null if non found
102: */
103: public static InProgressSubmission findSubmission(Context context,
104: String inProgressSubmissionID) throws SQLException {
105: char type = inProgressSubmissionID.charAt(0);
106: int id = Integer.valueOf(inProgressSubmissionID.substring(1));
107:
108: if (type == 'S') {
109: return WorkspaceItem.find(context, id);
110: } else if (type == 'W') {
111: return WorkflowItem.find(context, id);
112: }
113: return null;
114: }
115:
116: /**
117: * Return the workspace identified by the given id, the id should be
118: * prepended with the character S to signify that it is a workspace
119: * instead of a workflow.
120: *
121: * @param context
122: * @param inProgressSubmissionID
123: * @return The found workspaceitem or null if none found.
124: */
125: public static WorkspaceItem findWorkspace(Context context,
126: String inProgressSubmissionID) throws SQLException {
127: InProgressSubmission submission = findSubmission(context,
128: inProgressSubmissionID);
129: if (submission instanceof WorkspaceItem)
130: return (WorkspaceItem) submission;
131: return null;
132: }
133:
134: /**
135: * Return the workflow identified by the given id, the id should be
136: * prepended with the character S to signify that it is a workflow
137: * instead of a workspace.
138: *
139: * @param context
140: * @param inProgressSubmissionID
141: * @return The found workflowitem or null if none found.
142: */
143: public static WorkflowItem findWorkflow(Context context,
144: String inProgressSubmissionID) throws SQLException {
145: InProgressSubmission submission = findSubmission(context,
146: inProgressSubmissionID);
147: if (submission instanceof WorkflowItem)
148: return (WorkflowItem) submission;
149: return null;
150: }
151:
152: /**
153: * Obtains the submission info for the current submission process.
154: * If a submissionInfo object has already been created
155: * for this HTTP request, it is re-used, otherwise it is created.
156: *
157: * @param objectModel
158: * the cocoon Objectmodel
159: * @param workspaceID
160: * the workspaceID of the submission info to obtain
161: *
162: * @return a SubmissionInfo object
163: */
164: public static SubmissionInfo obtainSubmissionInfo(Map objectModel,
165: String workspaceID) throws SQLException {
166: Request request = ObjectModelHelper.getRequest(objectModel);
167: Context context = ContextUtil.obtainContext(objectModel);
168:
169: //try loading subInfo from HTTP request
170: SubmissionInfo subInfo = (SubmissionInfo) request
171: .getAttribute(DSPACE_SUBMISSION_INFO);
172:
173: //get the submission represented by the WorkspaceID
174: InProgressSubmission submission = findSubmission(context,
175: workspaceID);
176:
177: //if no submission info, or wrong submission info, reload it!
178: if ((subInfo == null && submission != null)
179: || (subInfo != null && submission != null && subInfo
180: .getSubmissionItem().getID() != submission
181: .getID())) {
182: try {
183: final HttpServletRequest httpRequest = (HttpServletRequest) objectModel
184: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
185:
186: // load submission info
187: subInfo = SubmissionInfo.load(httpRequest, submission);
188:
189: // Set the session ID
190: context.setExtraLogInfo("session_id="
191: + request.getSession().getId());
192:
193: // Store the submissionInfo in the request
194: request.setAttribute(DSPACE_SUBMISSION_INFO, subInfo);
195: } catch (Exception e) {
196: throw new SQLException(
197: "Error loading Submission Info: "
198: + e.getMessage());
199: }
200: } else if (subInfo == null && submission == null) {
201: throw new SQLException(
202: "Unable to load Submission Information, since WorkspaceID (ID:"
203: + workspaceID
204: + ") is not a valid in-process submission.");
205: }
206:
207: return subInfo;
208: }
209:
210: /**
211: * Indicate the user has advanced to the given page within a given step.
212: * This will only actually do anything when it's a user initially entering
213: * a submission. It will increase the "stage reached" and "page reached"
214: * columns - it will not "set back" where a user has reached.
215: *
216: * @param context The current DSpace content
217: * @param id The unique ID of the current workflow/workspace
218: * @param step the step the user has just reached
219: * @param page the page (within the step) the user has just reached
220: */
221: public static void setPageReached(Context context, String id,
222: int step, int page) throws SQLException,
223: AuthorizeException, IOException {
224: InProgressSubmission submission = findSubmission(context, id);
225:
226: if (submission instanceof WorkspaceItem) {
227: WorkspaceItem workspaceItem = (WorkspaceItem) submission;
228:
229: if (step > workspaceItem.getStageReached()) {
230: workspaceItem.setStageReached(step);
231: workspaceItem.setPageReached(1); //reset page to first page in new step
232: workspaceItem.update();
233: context.commit();
234: } else if ((step == workspaceItem.getStageReached())
235: && (page > workspaceItem.getPageReached())) {
236: workspaceItem.setPageReached(page);
237: workspaceItem.update();
238: context.commit();
239: }
240: }
241: }
242:
243: /**
244: * Find the maximum step the user has reached in the submission processes.
245: * If this submission is a workflow then return max-int.
246: *
247: * @param context The current DSpace content
248: * @param id The unique ID of the current workflow/workspace
249: */
250: public static int getMaximumStepReached(Context context, String id)
251: throws SQLException {
252:
253: InProgressSubmission submission = findSubmission(context, id);
254:
255: if (submission instanceof WorkspaceItem) {
256: WorkspaceItem workspaceItem = (WorkspaceItem) submission;
257: int stage = workspaceItem.getStageReached();
258: if (stage < 0)
259: stage = 0;
260: return stage;
261: }
262:
263: // This is a workflow, return infinity.
264: return Integer.MAX_VALUE;
265: }
266:
267: /**
268: * Find the maximum page (within the maximum step) that the user has
269: * reached in the submission processes.
270: * If this submission is a workflow then return max-int.
271: *
272: * @param context The current DSpace content
273: * @param id The unique ID of the current workflow/workspace
274: */
275: public static int getMaximumPageReached(Context context, String id)
276: throws SQLException {
277:
278: InProgressSubmission submission = findSubmission(context, id);
279:
280: if (submission instanceof WorkspaceItem) {
281: WorkspaceItem workspaceItem = (WorkspaceItem) submission;
282: int page = workspaceItem.getPageReached();
283: if (page < 0)
284: page = 0;
285: return page;
286: }
287:
288: // This is a workflow, return infinity.
289: return Integer.MAX_VALUE;
290: }
291:
292: /**
293: * Get current step number
294: *
295: * @param stepAndPage
296: * a double representing the current step and page
297: * (e.g. 1.2 is page 2 of step 1)
298: * @return step number
299: */
300: public static int getStep(double stepAndPage) {
301: //split step and page (e.g. 1.2 is page 2 of step 1)
302: String[] fields = Double.toString(stepAndPage).split("\\."); // split on period
303:
304: return Integer.parseInt(fields[0]);
305: }
306:
307: /**
308: * Get number of the current page within the current step
309: *
310: *@param stepAndPage
311: * a double representing the current step and page
312: * (e.g. 1.2 is page 2 of step 1)
313: * @return page number (within current step)
314: */
315: public static int getPage(double stepAndPage) {
316: //split step and page (e.g. 1.2 is page 2 of step 1)
317: String[] fields = Double.toString(stepAndPage).split("\\."); // split on period
318:
319: return Integer.parseInt(fields[1]);
320: }
321:
322: /**
323: * Process the save or remove step. If the user has selected to
324: * remove their submission then remove it.
325: *
326: * @param context The current DSpace content
327: * @param id The unique ID of the current workspace/workflow
328: * @param request The cocoon request object.
329: */
330: public static void processSaveOrRemove(Context context, String id,
331: Request request) throws SQLException, AuthorizeException,
332: IOException {
333: if (request.getParameter("submit_remove") != null) {
334: // If they selected to remove the item then delete everything.
335: WorkspaceItem workspace = findWorkspace(context, id);
336: workspace.deleteAll();
337: context.commit();
338: }
339: }
340:
341: /**
342: * Update the provided workflowItem to advance to the next workflow
343: * step. If this was the last thing needed before the item is
344: * committed to the repository then return true, otherwise false.
345: *
346: * @param context The current DSpace content
347: * @param id The unique ID of the current workflow
348: */
349: public static boolean processApproveTask(Context context, String id)
350: throws SQLException, UIException, ServletException,
351: AuthorizeException, IOException {
352: WorkflowItem workflowItem = findWorkflow(context, id);
353: Item item = workflowItem.getItem();
354:
355: // Advance the item along the workflow
356: WorkflowManager.advance(context, workflowItem, context
357: .getCurrentUser());
358:
359: // FIXME: This should be a return value from advance()
360: // See if that gave the item a Handle. If it did,
361: // the item made it into the archive, so we
362: // should display a suitable page.
363: String handle = HandleManager.findHandle(context, item);
364:
365: context.commit();
366:
367: if (handle != null) {
368: return true;
369: } else {
370: return false;
371: }
372: }
373:
374: /**
375: * Return the given task back to the pool of unclaimed tasks for another user
376: * to select and preform.
377: *
378: * @param context The current DSpace content
379: * @param id The unique ID of the current workflow
380: */
381: public static void processUnclaimTask(Context context, String id)
382: throws SQLException, UIException, ServletException,
383: AuthorizeException, IOException {
384: WorkflowItem workflowItem = findWorkflow(context, id);
385:
386: // Return task to pool
387: WorkflowManager.unclaim(context, workflowItem, context
388: .getCurrentUser());
389:
390: context.commit();
391:
392: //Log this unclaim action
393: log.info(LogManager.getHeader(context, "unclaim_workflow",
394: "workflow_item_id=" + workflowItem.getID()
395: + ",item_id=" + workflowItem.getItem().getID()
396: + ",collection_id="
397: + workflowItem.getCollection().getID()
398: + ",new_state=" + workflowItem.getState()));
399: }
400:
401: /**
402: * Claim this task from the pool of unclaimed task so that this user may
403: * preform the task by either approving or rejecting it.
404: *
405: * @param context The current DSpace content
406: * @param id The unique ID of the current workflow
407: */
408: public static void processClaimTask(Context context, String id)
409: throws SQLException, UIException, ServletException,
410: AuthorizeException, IOException {
411: WorkflowItem workflowItem = findWorkflow(context, id);
412:
413: // Claim the task
414: WorkflowManager.claim(context, workflowItem, context
415: .getCurrentUser());
416:
417: context.commit();
418:
419: //log this claim information
420: log.info(LogManager.getHeader(context, "claim_task",
421: "workflow_item_id=" + workflowItem.getID() + "item_id="
422: + workflowItem.getItem().getID()
423: + "collection_id="
424: + workflowItem.getCollection().getID()
425: + "newowner_id="
426: + workflowItem.getOwner().getID()
427: + "new_state=" + workflowItem.getState()));
428: }
429:
430: /**
431: * Reject the given task for the given reason. If the user did not provide
432: * a reason then an error is generated placing that field in error.
433: *
434: * @param context The current DSpace content
435: * @param id The unique ID of the current workflow
436: * @param request The current request object
437: */
438: public static String processRejectTask(Context context, String id,
439: Request request) throws SQLException, UIException,
440: ServletException, AuthorizeException, IOException {
441: WorkflowItem workflowItem = findWorkflow(context, id);
442:
443: String reason = request.getParameter("reason");
444:
445: if (reason != null && reason.length() > 1) {
446: WorkspaceItem wsi = WorkflowManager.reject(context,
447: workflowItem, context.getCurrentUser(), reason);
448:
449: //Load the Submission Process for the collection this WSI is associated with
450: Collection c = wsi.getCollection();
451: SubmissionConfigReader subConfigReader = new SubmissionConfigReader();
452: SubmissionConfig subConfig = subConfigReader
453: .getSubmissionConfig(c.getHandle(), false);
454:
455: // Set the "stage_reached" column on the workspace item
456: // to the LAST page of the LAST step in the submission process
457: // (i.e. the page just before "Complete", which is at NumSteps-1)
458: int lastStep = subConfig.getNumberOfSteps() - 2;
459: wsi.setStageReached(lastStep);
460: wsi
461: .setPageReached(AbstractProcessingStep.LAST_PAGE_REACHED);
462: wsi.update();
463:
464: context.commit();
465:
466: //Submission rejected. Log this information
467: log.info(LogManager.getHeader(context, "reject_workflow",
468: "workflow_item_id=" + wsi.getID() + "item_id="
469: + wsi.getItem().getID() + "collection_id="
470: + wsi.getCollection().getID()
471: + "eperson_id="
472: + context.getCurrentUser().getID()));
473:
474: // Return no errors.
475: return null;
476: } else {
477: // If the user did not supply a reason then
478: // place the reason field in error.
479: return "reason";
480: }
481: }
482:
483: /**
484: * Return the HTML / DRI field name for the given input.
485: *
486: * @param input
487: * @return field name as a String (e.g. dc_contributor_editor)
488: */
489: public static String getFieldName(DCInput input) {
490: String dcSchema = input.getSchema();
491: String dcElement = input.getElement();
492: String dcQualifier = input.getQualifier();
493: if (dcQualifier != null && !dcQualifier.equals(Item.ANY)) {
494: return dcSchema + "_" + dcElement + '_' + dcQualifier;
495: } else {
496: return dcSchema + "_" + dcElement;
497: }
498:
499: }
500:
501: /**
502: * Retrieves a list of all steps and pages within the
503: * current submission process.
504: * <P>
505: * This returns an array of Doubles of the form "#.#"
506: * where the former number is the step number, and the
507: * latter is the page number.
508: * <P>
509: * This list may differ from the list of steps in the
510: * progress bar if the current submission process includes
511: * non-interactive steps which do not appear in the progress bar!
512: * <P>
513: * This method is used by the Manakin submission flowscript
514: * (submission.js) to step forward/backward between steps.
515: *
516: * @param request
517: * The HTTP Servlet Request object
518: * @param subInfo
519: * the current SubmissionInfo object
520: *
521: */
522: public static Double[] getListOfAllSteps(
523: HttpServletRequest request, SubmissionInfo subInfo) {
524: ArrayList listStepNumbers = new ArrayList();
525:
526: // loop through all steps
527: for (int i = 0; i < subInfo.getSubmissionConfig()
528: .getNumberOfSteps(); i++) {
529: // get the current step info
530: SubmissionStepConfig currentStep = subInfo
531: .getSubmissionConfig().getStep(i);
532: String stepNumber = Integer.toString(currentStep
533: .getStepNumber());
534:
535: //Skip over the "Select Collection" step, since
536: // a user is never allowed to return to that step or jump from that step
537: if (currentStep.getId() != null
538: && currentStep
539: .getId()
540: .equals(
541: SubmissionStepConfig.SELECT_COLLECTION_STEP)) {
542: continue;
543: }
544:
545: // default to just one page in this step
546: int numPages = 1;
547:
548: try {
549: // load the processing class for this step
550: ClassLoader loader = subInfo.getClass()
551: .getClassLoader();
552: Class stepClass = loader.loadClass(currentStep
553: .getProcessingClassName());
554:
555: // call the "getNumberOfPages()" method of the class
556: // to get it's number of pages
557: AbstractProcessingStep step = (AbstractProcessingStep) stepClass
558: .newInstance();
559:
560: // get number of pages from servlet
561: numPages = step.getNumberOfPages(request, subInfo);
562: } catch (Exception e) {
563: log.error(
564: "Error loading step information from Step Class '"
565: + currentStep.getProcessingClassName()
566: + "' Error:", e);
567: }
568:
569: // save each of the step's pages to the progress bar
570: for (int j = 1; j <= numPages; j++) {
571: String stepAndPage = stepNumber + "." + j;
572:
573: Double stepAndPageNum = Double.valueOf(stepAndPage);
574:
575: listStepNumbers.add(stepAndPageNum);
576: }// end for each page
577: }// end for each step
578:
579: //convert into an array of Doubles
580: return (Double[]) listStepNumbers
581: .toArray(new Double[listStepNumbers.size()]);
582: }
583: }
|