/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.util;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.InProgressSubmission;
import org.dspace.submit.AbstractProcessingStep;
import org.dspace.workflow.WorkflowItem;
import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
/**
* Information about an item being editing with the submission UI
*
* @author Robert Tansley
* @version $Revision$
*/
public class SubmissionInfo
{
/** log4j logger */
private static Logger log = Logger.getLogger(SubmissionInfo.class);
/** The item which is being submitted */
private InProgressSubmission submissionItem = null;
/**
* The Submission process config, which holds all info about the submission
* process that this item is going through (including all steps, etc)
*/
private SubmissionConfig submissionConfig = null;
/**
* Handle of the collection where this item is being submitted
*/
private String collectionHandle = null;
/***************************************************************************
* Holds all information used to build the Progress Bar in a key,value set.
* Keys are the number of the step, followed by the number of the page
* within the step (e.g. "2.1" = The first page of Step 2) (e.g. "5.2" = The
* second page of Step 5) Values are the Headings to display for each step
* (e.g. "Describe")
**************************************************************************/
private Map<String, String> progressBar = null;
/** The element or element_qualifier to show more input boxes for */
private String moreBoxesFor;
/** The element or element_qualifier to scroll to initially using anchor */
private String jumpToField;
/** If non-empty, form-relative indices of missing fields */
private List<String> missingFields;
/** Specific bundle we're dealing with */
private Bundle bundle;
/** Specific bitstream we're dealing with */
private Bitstream bitstream;
/** Reader for submission process configuration file * */
private static SubmissionConfigReader submissionConfigReader;
/**
* Default Constructor - PRIVATE
* <p>
* Create a SubmissionInfo object
* using the load() method!
*
*/
private SubmissionInfo()
{
}
/**
* Loads all known submission information based on the given in progress
* submission and request object.
* <P>
* If subItem is null, then just loads the default submission information
* for a new submission.
*
* @param request
* The HTTP Servlet Request object
* @param subItem
* The in-progress submission we are loading information for
*
* @return a SubmissionInfo object
*
* @throws ServletException
* if an error occurs
*/
public static SubmissionInfo load(HttpServletRequest request, InProgressSubmission subItem) throws ServletException
{
boolean forceReload = false;
SubmissionInfo subInfo = new SubmissionInfo();
// load SubmissionConfigReader only the first time
// or if we're using a different UI now.
if (submissionConfigReader == null)
{
submissionConfigReader = new SubmissionConfigReader();
forceReload=true;
}
// save the item which is going through the submission process
subInfo.setSubmissionItem(subItem);
// Only if the submission item is created can we set its collection
String collectionHandle = SubmissionConfigReader.DEFAULT_COLLECTION;
if (subItem != null)
{
collectionHandle = subItem.getCollection().getHandle();
}
// save this collection handle to this submission info object
subInfo.setCollectionHandle(collectionHandle);
// load Submission Process config for this item's collection
// (Note: this also loads the Progress Bar info, since it is
// dependent on the Submission config)
loadSubmissionConfig(request, subInfo, forceReload);
return subInfo;
}
/**
* Is this submission in the workflow process?
*
* @return true if the current submission is in the workflow process
*/
public boolean isInWorkflow()
{
return ((this.submissionItem != null) && (this.submissionItem instanceof WorkflowItem || this.submissionItem instanceof XmlWorkflowItem));
}
/**
* Return the current in progress submission
*
* @return the InProgressSubmission object representing the current
* submission
*/
public InProgressSubmission getSubmissionItem()
{
return this.submissionItem;
}
/**
* Updates the current in progress submission item
*
* @param subItem
* the new InProgressSubmission object
*/
public void setSubmissionItem(InProgressSubmission subItem)
{
this.submissionItem = subItem;
}
/**
* Return the current submission process config (which includes all steps
* which need to be completed for the submission to be successful)
*
* @return the SubmissionConfig object, which contains info on all the steps
* in the current submission process
*/
public SubmissionConfig getSubmissionConfig()
{
return this.submissionConfig;
}
/**
* Causes the SubmissionConfig to be completely reloaded from the XML
* configuration file (item-submission.xml).
* <P>
* Note: This also reloads the progress bar info, since the progress bar
* depends entirely on the submission process (and its steps).
*
* @param request
* The HTTP Servlet Request object
*
* @throws ServletException
* if an error occurs
*/
public void reloadSubmissionConfig(HttpServletRequest request)
throws ServletException
{
// Only if the submission item is created can we set its collection
String collectionHandle = SubmissionConfigReader.DEFAULT_COLLECTION;
if (this.submissionItem != null)
{
collectionHandle = submissionItem.getCollection().getHandle();
}
this.setCollectionHandle(collectionHandle);
// force a reload of the submission process configuration
loadSubmissionConfig(request, this, true);
}
/**
* Returns a particular global step definition based on its ID.
* <P>
* Global step definitions are those defined in the <step-definitions>
* section of the configuration file.
*
* @param stepID
* step's identifier
*
* @return the SubmissionStepConfig representing the step
*
* @throws ServletException
* if no default submission process configuration defined
*/
public SubmissionStepConfig getStepConfig(String stepID)
throws ServletException
{
return submissionConfigReader.getStepConfig(stepID);
}
/**
* Return text information suitable for logging.
* <p>
* This method is used by several of the Step classes
* to log major events during the submission process (e.g. when
* license agreement was accepted, when item was submitted,
* when it was available in DSpace, etc.)
*
* @return the type and ID of the submission, bundle and/or bitstream for
* logging
*/
public String getSubmissionLogInfo()
{
String info = "";
if (isInWorkflow())
{
info = info + "workflow_id=" + getSubmissionItem().getID();
}
else
{
info = info + "workspace_item_id" + getSubmissionItem().getID();
}
if (getBundle() != null)
{
info = info + ",bundle_id=" + getBundle().getID();
}
if (getBitstream() != null)
{
info = info + ",bitstream_id=" + getBitstream().getID();
}
return info;
}
/**
* Gets the handle of the collection to which this item is being submitted
*
* @return the collection handle
*/
public String getCollectionHandle()
{
return this.collectionHandle;
}
/**
* Sets the handle of the collection to which this item is being submitted
*
* @param handle
* the new collection handle
*/
public void setCollectionHandle(String handle)
{
this.collectionHandle = handle;
}
/**
* Return the information used to build the progress bar (this includes all
* the steps in this submission, as well as the ordering and names of the
* steps).
* <p>
* Returns a Hashmap, with the following specifics:
* <p>
* Keys are the number of the step, followed by the number of the page
* within the step
* <p>
* (e.g. "2.1" = The first page of Step 2)
* <p>
* (e.g. "5.2" = The second page of Step 5)
* <P>
* Values are the Headings to display for each step (e.g. "Describe")
*
* @return a Hashmap of Progress Bar information.
*/
public Map<String, String> getProgressBarInfo()
{
return this.progressBar;
}
/**
* Return the current bitstream we're working with (This is used during
* upload processes, or user interfaces that are dealing with bitstreams)
*
* @return the Bitstream object for the bitstream
*/
public Bitstream getBitstream()
{
return this.bitstream;
}
/**
* Sets the current bitstream we're working with (This is used during upload
* processes, or user interfaces that are dealing with bitstreams)
*
* @param bits
* the bitstream
*/
public void setBitstream(Bitstream bits)
{
this.bitstream = bits;
}
/**
* Return the current bundle we're working with (This is used during upload
* processes, or user interfaces that are dealing with bundles/bitstreams)
*
* @return the Bundle object for the bundle
*/
public Bundle getBundle()
{
return this.bundle;
}
/**
* Sets the current bundle we're working with (This is used during upload
* processes, or user interfaces that are dealing with bundles/bitstreams)
*
* @param bund
* the bundle
*/
public void setBundle(Bundle bund)
{
this.bundle = bund;
}
/**
* Return form related indices of the required fields which were not filled
* out by the user.
*
* @return a List of empty fields which are required
*/
public List<String> getMissingFields()
{
return this.missingFields;
}
/**
* Sets the form related indices of the required fields which were not
* filled out by the user.
*
* @param missing
* the List of empty fields which are required
*/
public void setMissingFields(List<String> missing)
{
this.missingFields = missing;
}
/**
* Return metadata field which user has requested more input boxes be
* displayed (by pressing "Add More" on one of the "Describe" pages)
*
* @return the String name of the field element
*/
public String getMoreBoxesFor()
{
return this.moreBoxesFor;
}
/**
* Sets the metadata field which user has requested more input boxes be
* displayed (by pressing "Add More" on one of the "Describe" pages)
*
* @param fieldname
* the name of the field element on the page
*/
public void setMoreBoxesFor(String fieldname)
{
this.moreBoxesFor = fieldname;
}
/**
* Return metadata field which JSP should "jump to" (i.e. set focus on) when
* the JSP next loads. This is used during the Describe step.
*
* @return the String name of the field element
*/
public String getJumpToField()
{
return this.jumpToField;
}
/**
* Sets metadata field which JSP should "jump to" (i.e. set focus on) when
* the JSP next loads. This is used during the Describe step.
*
* @param fieldname
* the name of the field on the page
*/
public void setJumpToField(String fieldname)
{
this.jumpToField = fieldname;
}
/**
* Load necessary information to build the Progress Bar for the Item
* Submission Progress.
*
* This information is returned in the form of a HashMap (which is then
* stored as a part of the SubmissionInfo). The HashMap takes the following
* form:
*
* Keys - the number of the step, followed by the number of the page within
* the step (e.g. "2.1" = The first page of Step 2) (e.g. "5.2" = The second
* page of Step 5)
*
* Values - the headings to display for each step (e.g. "Describe",
* "Verify")
*
* @param request
* The HTTP Servlet Request object
* @param subInfo
* the SubmissionInfo object we are loading into
* @param forceReload
* If true, this method reloads from scratch (and overwrites
* cached progress bar info)
*
*/
private static void loadProgressBar(HttpServletRequest request,
SubmissionInfo subInfo, boolean forceReload)
{
Map<String, String> progressBarInfo = null;
log.debug("Loading Progress Bar Info");
if (!forceReload)
{
// first, attempt to load from cache
progressBarInfo = loadProgressBarFromCache(request
.getSession());
}
if (progressBarInfo != null && log.isDebugEnabled())
{
log.debug("Found Progress Bar Info in cache: "
+ progressBarInfo.size()
+ " pages to display in progress bar");
}
// if unable to load from cache, must load from scratch
else
{
progressBarInfo = new LinkedHashMap<String, String>();
// loop through all steps
for (int i = 0; i < subInfo.submissionConfig.getNumberOfSteps(); i++)
{
// get the current step info
SubmissionStepConfig currentStep = subInfo.submissionConfig
.getStep(i);
String stepNumber = Integer.toString(currentStep
.getStepNumber());
String stepHeading = currentStep.getHeading();
// as long as this step is visible, include it in
// the Progress Bar
if (currentStep.isVisible())
{
// 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<AbstractProcessingStep> stepClass = (Class<AbstractProcessingStep>)loader.loadClass(currentStep.getProcessingClassName());
// call the "getNumberOfPages()" method of the class
// to get it's number of pages
AbstractProcessingStep step = stepClass.newInstance();
// get number of pages from servlet
numPages = step.getNumberOfPages(request, subInfo);
}
catch (Exception e)
{
log.error(
"Error loading progress bar 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 pageNumber = Integer.toString(j);
// store ("stepNumber.pageNumber", Heading) for each
// page in the step
progressBarInfo.put(stepNumber + "." + pageNumber,
stepHeading);
}// end for each page
}
}// end for each step
log.debug("Loaded Progress Bar Info from scratch: "
+ progressBarInfo.size()
+ " pages to display in progress bar");
// cache this new progress bar
saveProgressBarToCache(request.getSession(), progressBarInfo);
}// end if null
// save progressBarInfo to submission Info
subInfo.progressBar = progressBarInfo;
}
/**
* Saves all progress bar information into session cache. This saves us from
* having to reload this same progress bar over and over again.
*
* @param session
* The HTTP Session object
* @param progressBarInfo
* The progress bar info to cache
*
*/
private static void saveProgressBarToCache(HttpSession session,
Map<String, String> progressBarInfo)
{
// cache progress bar info to Session
session.setAttribute("submission.progressbar", progressBarInfo);
}
/**
* Attempts to retrieve progress bar information (for a particular
* collection) from session cache.
*
* If the progress bar info cannot be found, returns null
*
* @param session
* The HTTP Session object
*
* @return progressBarInfo HashMap (if found), or null (if not)
*
*/
private static Map<String, String> loadProgressBarFromCache(HttpSession session)
{
return (Map<String, String>) session.getAttribute("submission.progressbar");
}
/**
* Loads SubmissionConfig object for the given submission info object. If a
* SubmissionConfig object cannot be loaded, a Servlet Error is thrown.
* <p>
* This method just loads this SubmissionConfig object internally, so that
* it is available via a call to "getSubmissionConfig()"
*
* @param request
* The HTTP Servlet Request object
* @param subInfo
* the SubmissionInfo object we are loading into
* @param forceReload
* If true, this method reloads from scratch (and overwrites
* cached SubmissionConfig)
*
*/
private static void loadSubmissionConfig(HttpServletRequest request,
SubmissionInfo subInfo, boolean forceReload)
throws ServletException
{
log.debug("Loading Submission Config information");
if (!forceReload)
{
// first, try to load from cache
subInfo.submissionConfig = loadSubmissionConfigFromCache(request
.getSession(), subInfo.getCollectionHandle(), subInfo
.isInWorkflow());
}
if (subInfo.submissionConfig == null || forceReload)
{
// reload the proper Submission process config
// (by reading the XML config file)
subInfo.submissionConfig = submissionConfigReader
.getSubmissionConfig(subInfo.getCollectionHandle(), subInfo
.isInWorkflow());
// cache this new submission process configuration
saveSubmissionConfigToCache(request.getSession(),
subInfo.submissionConfig, subInfo.getCollectionHandle(),
subInfo.isInWorkflow());
// also must force reload Progress Bar info,
// since it's based on the Submission config
loadProgressBar(request, subInfo, true);
}
else
{
log.debug("Found Submission Config in session cache!");
// try and reload progress bar from cache
loadProgressBar(request, subInfo, false);
}
}
/**
* Saves SubmissionConfig object into session cache. This saves us from
* having to reload this object during every "Step".
*
* @param session
* The HTTP Session object
* @param subConfig
* The SubmissionConfig to cache
* @param collectionHandle
* The Collection handle this SubmissionConfig corresponds to
* @param isWorkflow
* Whether this SubmissionConfig corresponds to a workflow
*
*
*/
private static void saveSubmissionConfigToCache(HttpSession session,
SubmissionConfig subConfig, String collectionHandle,
boolean isWorkflow)
{
// cache the submission process config
// and the collection it corresponds to
session.setAttribute("submission.config", subConfig);
session.setAttribute("submission.config.collection", collectionHandle);
session.setAttribute("submission.config.isWorkflow", Boolean.valueOf(
isWorkflow));
}
/**
* Loads SubmissionConfig object from session cache for the given
* Collection. If a SubmissionConfig object cannot be found, null is
* returned.
*
* @param session
* The HTTP Session object
* @param collectionHandle
* The Collection handle of the SubmissionConfig to load
* @param isWorkflow
* whether or not we loading the Submission process for a
* workflow item
*
* @return The cached SubmissionConfig for this collection
*/
private static SubmissionConfig loadSubmissionConfigFromCache(
HttpSession session, String collectionHandle, boolean isWorkflow)
{
// attempt to load submission process config
// from cache for the current collection
String cachedHandle = (String) session
.getAttribute("submission.config.collection");
Boolean cachedIsWorkflow = (Boolean) session
.getAttribute("submission.config.isWorkflow");
// only load from cache if the collection handle and
// workflow item status both match!
if (collectionHandle.equals(cachedHandle)
&& isWorkflow == cachedIsWorkflow.booleanValue())
{
return (SubmissionConfig) session.getAttribute("submission.config");
}
else
{
return null;
}
}
}