org.webcurator.core.archive.dps.DPSArchive.java Source code

Java tutorial

Introduction

Here is the source code for org.webcurator.core.archive.dps.DPSArchive.java

Source

/**
 * org.webcurator.core.archive.dps - Software License
 *
 * Copyright 2007/2009 National Library of New Zealand.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *
 * or the file "LICENSE.txt" included with the software.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

package org.webcurator.core.archive.dps;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.webcurator.core.archive.BaseArchive;
import org.webcurator.core.archive.ArchiveFile;
import static org.webcurator.core.archive.Constants.ACCESS_RESTRICTION;
import static org.webcurator.core.archive.Constants.REFERENCE_NUMBER;

import org.webcurator.core.archive.SIPUtils;
import org.webcurator.core.archive.dps.DpsDepositFacade.DepositResult;
import org.webcurator.domain.model.core.CustomDepositFormCriteriaDTO;
import org.webcurator.domain.model.core.CustomDepositFormResultDTO;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlException;

import com.exlibris.digitool.deposit.service.xmlbeans.DepositDataDocument;
import com.exlibris.digitool.deposit.service.xmlbeans.DepositDataDocument.DepositData;
//import com.exlibris.digitool.locator.WebServiceLocator;
import com.exlibris.dps.sdk.producer.ProducerWebServices;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.List;
import java.util.HashMap;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

/**
 * A specific DPS-based archiver for the National Library of New Zealand's DPS archival system.
 *
 * @author Nicolai Moles-Benfell
 */
public class DPSArchive extends BaseArchive {
    /**
     * A very light-weight version of the com.exlibris.digitool.deposit.service.xmlbeans.DepData
     * class, so that this can be stored in the cache much more efficiently and with less
     * memory. 
     * @author pushpar
     *
     */
    public static class DepData {
        public final String id;
        public final String description;

        protected DepData(String id, String description) {
            this.id = id;
            this.description = description;
        }
    }

    private static Log log = LogFactory.getLog(DPSArchive.class);

    private static Cache producerCache;
    private java.lang.String pdsUrl;
    private java.lang.String ftpHost;
    private java.lang.String ftpUserName;
    private java.lang.String ftpPassword;
    private java.lang.String ftpDirectory;
    private java.lang.String depositServerBaseUrl;
    private java.lang.String producerWsdlRelativePath;
    private java.lang.String depositWsdlRelativePath;
    private java.lang.String producerWsdlUrl;
    private java.lang.String dpsUserInstitution;
    private java.lang.String dpsUserName;
    private java.lang.String dpsUserPassword;
    private java.lang.String materialFlowId;
    private java.lang.String producerId;
    private java.lang.String omsOpenAccess;
    private java.lang.String omsPublishedRestricted = "";
    private java.lang.String omsUnpublishedRestrictedByLocation = "";
    private java.lang.String omsUnpublishedRestrictedByPersion = "";
    private List<String> agenciesResponsibleForHtmlSerials = new ArrayList<String>();
    private List<String> targetDCTypesOfHtmlSerials = new ArrayList<String>();
    private List<String> materialFlowsOfHtmlSerials = new ArrayList<String>();
    private List<String> ieEntityTypesOfHtmlSerials = new ArrayList<String>();
    private List<String> customDepositFormURLsForHtmlSerialIngest;
    private static final String DPS_SIPID_PREFIX = "dps-sipid-";

    static {
        // Set up ehcache for the use of custom deposit form to store mapping between producer agents and producers
        try {
            String cacheName = "RosettaProducerCache";
            int maxElementsInMemory = 500; // Hopefully, there are only these many producer agents using this custom form facility
            boolean overflowToDisk = false;
            boolean eternal = false;
            long timeToLiveSeconds = 604800; // Each object will live for 7 days == 7 * 60 * 60 * 24 seconds
            long timeToIdleSeconds = 0;
            producerCache = new Cache(cacheName, maxElementsInMemory, overflowToDisk, eternal, timeToLiveSeconds,
                    timeToIdleSeconds);
            CacheManager cacheManager = CacheManager.create();
            cacheManager.addCache(producerCache);
        } catch (Exception e) {
            log.error("Error creating an ehCache for producer/producer agent caching", e);
        }
    }

    /**
     * @param targetInstanceOID The target instance oid
     * @param SIP               The METS xml structure for completion and archival
     * @param xAttributes       Any extra attributes that may be required for archival (generally contains parameters for OMS meta-data)
     * @param fileList          A list of files (@see org.webcurator.core.archive.ArchiveFile) to archive
     * @return A unique archive identifier (IID returned form the OMS system)
     * @throws DPSUploadException
     */
    @SuppressWarnings("unchecked")
    public String submitToArchive(String targetInstanceOID, String SIP, Map xAttributes, List<ArchiveFile> fileList)
            throws DPSUploadException {
        String IID = null;

        try {
            if (targetInstanceOID != null) {
                for (ArchiveFile f : fileList) {
                    f.setMd5(calculateMD5(f.getFile()));
                }
                String finalSIP = getFinalSIP(SIP, targetInstanceOID, fileList);

                DpsDepositFacade dpsDeposit = getDpsDepositFacade();

                List<File> files = extractFileDetailsFrom(fileList);

                Map<String, String> parameters = populateDepositParameterFromFields(xAttributes, finalSIP,
                        targetInstanceOID);

                DepositResult depositResult = dpsDeposit.deposit(parameters, files);

                if (depositResult.isError())
                    throw new DPSUploadException("Submission to DPS failed for Target Instance " + targetInstanceOID
                            + ", message from DPS: " + depositResult.getMessageDesciption());

                IID = DPS_SIPID_PREFIX + Long.toString(depositResult.getSipId());
            }
            return IID;
        } catch (Exception ex) {
            log.error("Error submitting to DPS", ex);
            throw new DPSUploadException(ex);
        }

    }

    public CustomDepositFormResultDTO getCustomDepositFormDetails(CustomDepositFormCriteriaDTO criteria) {
        log.debug("DPSArchive: getCustomDepositFormDetails() invoked with " + criteria);
        CustomDepositFormResultDTO response = new CustomDepositFormResultDTO();
        if (criteria == null)
            return response;
        /*
         * Current logic of determining whether there is a need to fill a custom deposit form:
         * 
         * A custom deposit form is required if:
         * - You are archiving a harvest of (target) type "HTML Serial", as
         *   dictated by the configured target type names in targetTypesOfHtmlSerials.
         * OR,
         * - Your login id belongs to the agency responsible for harvesting/archiving 
         *   HTML Serials, as dictated by the configured agency names in
         *   agenciesResponsibleForHtmlSerials.
         * 
         * Also, if your login belongs to the agency responsible for harvesting/archiving
         * HTML Serials and if you are NOT archiving a harvest of (target) type "HTML Serial",
         * the custom form selected will be an "Error" custom form which is configured in 
         * the system as rosetta_custom_deposit_form_invalid_dctype.jsp
         * 
         * Note: All comparisons of target types or agency names are case-insensitive.
         */
        int targetTypeIndex = getIndexInList(criteria.getTargetType(), targetDCTypesOfHtmlSerials);
        boolean isAgencyResponsibleForHtmlSerials = containsInList(criteria.getAgencyName(),
                agenciesResponsibleForHtmlSerials);
        boolean isTargetDCTypeOfHtmlSerials = (targetTypeIndex >= 0);
        response.setCustomDepositFormRequired(false);
        if (isTargetDCTypeOfHtmlSerials) {
            /*
             * Target type is of HTML Serial - will show custom form irrespective of which agency you belong to.
             * The selected custom form will be based on the type of target, as configured in the wct-das.xml
             * and wct-das.properties.
             */
            response.setCustomDepositFormRequired(true);
            response.setUrlForCustomDepositForm(customDepositFormURLsForHtmlSerialIngest.get(targetTypeIndex));
        } else {
            /*
             * Do not show custom form if you do not belong to the agency responsible for HTML serials.
             * But if you do belong, then show an "invalid/error" page. 
             */
            if (isAgencyResponsibleForHtmlSerials) {
                response.setCustomDepositFormRequired(true);
                response.setUrlForCustomDepositForm(
                        "/wct-store/customDepositForms/rosetta_custom_deposit_form_invalid_dctype.jsp");
            }
        }
        return response;
    }

    public boolean validateProducerAgentName(String producerAgent) {
        try {
            ProducerWebServices _pws = getProducerWebServices();
            String producerAgentId = _pws.getInternalUserIdByExternalId(producerAgent);
            if (producerAgentId == null)
                return false;
            else
                return true;
        } catch (Exception e) {
            log.error("Error getting producer agent id for agent user name " + producerAgent + " from URL "
                    + producerWsdlUrl, e);
            return false;
        }
    }

    public boolean isLoginSuccessful(String producerAgent, String agentPassword) {
        Map<String, String> parameterMap = new HashMap<String, String>();
        parameterMap.put(DpsDepositFacade.DPS_INSTITUTION, this.dpsUserInstitution);
        parameterMap.put(DpsDepositFacade.DPS_USER_NAME, producerAgent);
        parameterMap.put(DpsDepositFacade.DPS_PASSWORD, agentPassword);
        parameterMap.put(DpsDepositFacade.PDS_URL, this.pdsUrl);
        DpsDepositFacade dpsDeposit = getDpsDepositFacade();
        String pdsSessionId = null;
        try {
            pdsSessionId = dpsDeposit.loginToPDS(parameterMap);
        } catch (Exception e) {
            log.error("Error logging in to PDS for agent " + producerAgent + ". Exception: " + e);
        }
        if (pdsSessionId == null)
            return false;
        return true;
    }

    public DepData[] getProducer(String producerAgentUserId, boolean fromCache) {
        if (fromCache) {
            DepData[] depData = getProducerDataFromCache(producerAgentUserId);
            if (depData != null && depData.length > 0) {
                log.debug("Retrieved producer data from ehcache for producer agent " + producerAgentUserId);
                return depData;
            }
        }
        try {
            ProducerWebServices _pws = getProducerWebServices();
            String producerAgentId = _pws.getInternalUserIdByExternalId(producerAgentUserId);
            log.debug("Producer agent id for the user id " + producerAgentUserId + ": " + producerAgentId);
            if (producerAgentId == null)
                return null;
            String xmlReply = _pws.getProducersOfProducerAgent(producerAgentId);
            DepData[] depData = xmlReplyToDepData(xmlReply);
            putProducerDataToCache(producerAgentUserId, depData);
            if (log.isDebugEnabled()) {
                log.debug("Producers for agent " + producerAgentId + ":");
                for (DepData data : depData) {
                    log.debug("\tdata.id = " + data.id + ", description: " + data.description);
                }
            }
            return depData;
        } catch (Exception e) {
            log.error("Error getting producers for agent " + producerAgentUserId + " from URL " + producerWsdlUrl,
                    e);
        }
        return null;
    }

    public DepData[] getMaterialFlows(String producerID) {
        try {
            ProducerWebServices _pws = getProducerWebServices();
            String xmlReply = _pws.getMaterialFlowsOfProducer(producerID);
            DepData[] depData = xmlReplyToDepData(xmlReply);
            if (log.isDebugEnabled()) {
                log.debug("Material flows for producers " + producerID + ":");
                for (DepData data : depData) {
                    log.debug("\tdata.id = " + data.id + ", description: " + data.description);
                }
            }
            return depData;
        } catch (Exception e) {
            log.error("Error getting material flows for producer " + producerID + " from URL " + producerWsdlUrl,
                    e);
        }
        return null;
    }

    public boolean validateMaterialFlowAssociation(String producerId, String targetDcType) {
        String materialFlowOfTargetDcType = getMaterialFlowOfTargetDCType(targetDcType);
        if (materialFlowOfTargetDcType == null || materialFlowOfTargetDcType.length() <= 0)
            return false;
        DepData[] materialFlows = getMaterialFlows(producerId);
        if (materialFlows == null || materialFlows.length <= 0) {
            log.error("Could not get any material flows from Rosetta for producer " + producerId + " using URL "
                    + producerWsdlUrl);
            return false;
        }
        for (int i = 0; i < materialFlows.length; i++) {
            DepData anMF = materialFlows[i];
            if (materialFlowOfTargetDcType.equals(anMF.id)) {
                return true;
            }
        }
        return false;
    }

    private String getMaterialFlowOfTargetDCType(String targetDcType) {
        return locatePropertyAgainstTargetDCType(targetDcType, materialFlowsOfHtmlSerials);
    }

    private String getIeEntityTypeOfTargetDCType(String targetDcType) {
        return locatePropertyAgainstTargetDCType(targetDcType, ieEntityTypesOfHtmlSerials);
    }

    private String locatePropertyAgainstTargetDCType(String targetDcType, List<String> propertyList) {
        String propertyAgainstTargetDCType = null;
        int targetTypeIndex = getIndexInList(targetDcType, targetDCTypesOfHtmlSerials);
        if (targetTypeIndex < 0) {
            log.error("DC Type " + targetDcType + " of the target instance is not of an HTML serial type");
            return null;
        }
        try {
            propertyAgainstTargetDCType = propertyList.get(targetTypeIndex);
        } catch (Exception ex) {
            log.error("Error getting a property corresponding to the DC Type " + targetDcType + " from list "
                    + propertyList, ex);
        }
        return (propertyAgainstTargetDCType == null) ? null : propertyAgainstTargetDCType.trim();
    }

    private DepData[] xmlReplyToDepData(String xmlReply) throws XmlException {
        DepositDataDocument depositReply = DepositDataDocument.Factory.parse(xmlReply);
        DepositData depositData = depositReply.getDepositData();
        DepData[] depData = sortByDescription(depositData);
        return depData;
    }

    private ProducerWebServices getProducerWebServices() {
        synchronized (this) {
            if (producerWsdlUrl == null) {
                if (this.depositServerBaseUrl != null && producerWsdlRelativePath != null) {
                    producerWsdlUrl = this.depositServerBaseUrl + producerWsdlRelativePath;
                }
            }
        }
        /*
         * Ideally, we should be using the following call:
         * return WebServiceLocator.getInstance().lookUp(ProducerWebServices.class, producerWsdlUrl);
         * which uses the DPS SDK class com.exlibris.digitool.locator.WebServiceLocator.
         * 
         * However, the WebServiceLocator class from DPS SDK seems to be caching the web service
         * handle and this handle, when not used for several hours, seems to become stale, throwing
         * up exceptions.
         * 
         * So instead of using the DPS SDK's WebServiceLocator class, we are forced to use the 
         * following low-level logic to bind to the Producer web service. The following code 
         * has been created by copying the required code segment from the
         * WebServiceLocator.lookUp() method by ExLibris.
         */
        String serviceEndpointInterfaceName = ProducerWebServices.class.getSimpleName();
        URL wsdlUrl = null;
        String wsdlUrlStr = producerWsdlUrl;
        try {
            wsdlUrl = new URL(wsdlUrlStr);
        } catch (Exception e) {
            log.error("Failed to build WSDL URL from " + wsdlUrlStr + " - Web service lookup of "
                    + serviceEndpointInterfaceName + " will fail", e);
            e.printStackTrace();
        }
        QName serviceName = new QName("http://dps.exlibris.com/", serviceEndpointInterfaceName);
        Service service = Service.create(wsdlUrl, serviceName);
        QName portName = new QName("http://dps.exlibris.com/",
                (new StringBuilder()).append(serviceEndpointInterfaceName).append("Port").toString());
        return service.getPort(portName, ProducerWebServices.class);
    }

    private DepData[] sortByDescription(DepositData depositData) {
        com.exlibris.digitool.deposit.service.xmlbeans.DepData[] depDataFromRosetta = depositData.getDepDataArray();
        if (depDataFromRosetta == null)
            return null;
        DepData depdata[] = new DepData[depDataFromRosetta.length];
        for (int i = 0; i < depDataFromRosetta.length; i++) {
            depdata[i] = new DepData(depDataFromRosetta[i].getId(), depDataFromRosetta[i].getDescription());
        }
        Arrays.sort(depdata, new DepDataComparator());
        return depdata;
    }

    private static DepData[] getProducerDataFromCache(String producerAgentUserId) {
        try {
            if (producerCache == null)
                return null;
            Element cacheElement;
            synchronized (producerCache) {
                cacheElement = producerCache.get(producerAgentUserId);
            }
            if (cacheElement == null)
                return null;
            return (DepData[]) cacheElement.getValue();
        } catch (Exception e) {
            log.warn("Error getting producer data from cache for the producer agent " + producerAgentUserId, e);
            return null;
        }
    }

    private static void putProducerDataToCache(String producerAgentUserId, DepData[] depData) {
        try {
            if (producerCache == null)
                return;
            Element cacheElement = new Element(producerAgentUserId, depData);
            synchronized (producerCache) {
                producerCache.put(cacheElement);
            }
        } catch (Exception e) {
            log.warn("Error putting producer data into cache for the producer agent " + producerAgentUserId, e);
        }
    }

    protected String calculateMD5(File file) throws FileNotFoundException {
        return FixityUtils.calculateMD5(file);
    }

    /**
     * Gets an instance of DpsDepositFacade. Make sure that the wct-submit-to-rosetta.jar
     * JAR file (created by building WCTSubmitToRosetta module) is in the class path.
     */
    protected DpsDepositFacade getDpsDepositFacade() {
        return new nz.govt.natlib.ndha.wctdpsdepositor.DpsDepositProxy();
    }

    protected List<File> extractFileDetailsFrom(List<ArchiveFile> fileList) {
        List<File> files = new ArrayList<File>(fileList.size());
        for (ArchiveFile archiveFile : fileList)
            files.add(archiveFile.getFile());
        return files;
    }

    protected String getFinalSIP(String sip, String targetInstanceOID, List<ArchiveFile> files) {
        return SIPUtils.finishSIP(sip, targetInstanceOID, files, true);
    }

    public void setPdsUrl(String pdsUrl) {
        this.pdsUrl = pdsUrl;
    }

    public void setFtpHost(String ftpHost) {
        this.ftpHost = ftpHost;
    }

    public void setFtpUserName(String ftpUserName) {
        this.ftpUserName = ftpUserName;
    }

    public void setFtpPassword(String ftpPassword) {
        this.ftpPassword = ftpPassword;
    }

    public void setFtpDirectory(String ftpDirectory) {
        this.ftpDirectory = ftpDirectory;
    }

    public void setDepositServerBaseUrl(String depositServerBaseUrl) {
        this.depositServerBaseUrl = depositServerBaseUrl;
    }

    public void setDpsUserInstitution(String dpsUserInstitution) {
        this.dpsUserInstitution = dpsUserInstitution;
    }

    public void setDpsUserName(String dpsUserName) {
        this.dpsUserName = dpsUserName;
    }

    public void setDpsUserPassword(String dpsUserPassword) {
        this.dpsUserPassword = dpsUserPassword;
    }

    public void setMaterialFlowId(String materialFlowId) {
        this.materialFlowId = materialFlowId;
    }

    public void setProducerId(String producerId) {
        this.producerId = producerId;
    }

    public void setProducerWsdlRelativePath(String producerWsdlRelativePath) {
        this.producerWsdlRelativePath = producerWsdlRelativePath;
    }

    public void setDepositWsdlRelativePath(String depositWsdlRelativePath) {
        this.depositWsdlRelativePath = depositWsdlRelativePath;
    }

    public void setAgenciesResponsibleForHtmlSerials(String agenciesResponsibleForHtmlSerials) {
        this.agenciesResponsibleForHtmlSerials = toListOfLowerCaseValues(agenciesResponsibleForHtmlSerials);
    }

    public void setTargetDCTypesOfHtmlSerials(String targetDCTypesOfHtmlSerials) {
        this.targetDCTypesOfHtmlSerials = toListOfLowerCaseValues(targetDCTypesOfHtmlSerials);
    }

    public void setMaterialFlowsOfHtmlSerials(String materialFlowsOfHtmlSerials) {
        this.materialFlowsOfHtmlSerials = toList(materialFlowsOfHtmlSerials);
    }

    public void setIeEntityTypesOfHtmlSerials(String ieEntityTypesOfHtmlSerials) {
        this.ieEntityTypesOfHtmlSerials = toList(ieEntityTypesOfHtmlSerials);
    }

    public void setCustomDepositFormURLsForHtmlSerialIngest(String customDepositFormURLsForHtmlSerialIngest) {
        this.customDepositFormURLsForHtmlSerialIngest = toList(customDepositFormURLsForHtmlSerialIngest);
    }

    public void setOmsOpenAccess(String omsOpenAccess) {
        this.omsOpenAccess = omsOpenAccess;
    }

    public void setOmsPublishedRestricted(String omsPublishedRestricted) {
        this.omsPublishedRestricted = omsPublishedRestricted;
    }

    public void setOmsUnpublishedRestrictedByLocation(String omsUnpublishedRestrictedByLocation) {
        this.omsUnpublishedRestrictedByLocation = omsUnpublishedRestrictedByLocation;
    }

    public void setOmsUnpublishedRestrictedByPersion(String omsUnpublishedRestrictedByPersion) {
        this.omsUnpublishedRestrictedByPersion = omsUnpublishedRestrictedByPersion;
    }

    /**
     * Converts a comma-separated string into a list of lower-case letter strings.
     * 
     * @param commaSeparatedString
     * @return
     */
    protected static List<String> toListOfLowerCaseValues(String commaSeparatedString) {
        List<String> theList = new ArrayList<String>();
        if (commaSeparatedString == null)
            return theList;

        String[] tokens = commaSeparatedString.split("[,]");
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            if (token == null || token.trim().length() <= 0)
                continue;
            theList.add(token.trim().toLowerCase());
        }
        return theList;
    }

    /**
     * Converts a comma-separated string into a list of strings.
     * 
     * @param commaSeparatedString
     * @return
     */
    protected static List<String> toList(String commaSeparatedString) {
        List<String> theList = new ArrayList<String>();
        if (commaSeparatedString == null)
            return theList;

        String[] tokens = commaSeparatedString.split("[,]");
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            if (token == null || token.trim().length() <= 0)
                continue;
            theList.add(token.trim());
        }
        return theList;
    }

    /**
     * Makes a case-insensitive search inside <code>aList</code> to see 
     * if <code>aValue</code> is present in the list, and returns the index
     * of the item. 
     * @param aList
     * @param aValue
     * @return Index of the item if it is present in the list, -1 otherwise
     */
    protected int getIndexInList(String aValue, List<String> aList) {
        if (aValue == null || aValue.trim().length() <= 0)
            return -1;
        if (aList == null || aList.isEmpty())
            return -1;
        String aValueLC = aValue.toLowerCase();
        return aList.indexOf(aValueLC);
    }

    /**
     * Makes a case-insensitive search inside <code>aList</code> to see 
     * if <code>aValue</code> is present in the list. 
     * @param aList
     * @param aValue
     * @return
     */
    protected boolean containsInList(String aValue, List<String> aList) {
        return (getIndexInList(aValue, aList) >= 0) ? true : false;
    }

    protected Map<String, String> populateDepositParameterFromFields(Map<?, ?> attributes, String finalSIP,
            String targetInstanceOID) {
        Map<String, String> parameterMap = new HashMap<String, String>();

        /*
         * Add the parameters read from wct-das.xml file. However, if the
         * custom deposit form is filled by the user, get some of these 
         * parameters from what the user filled in the custom form.
         */
        String dpsUserNameToUse = this.dpsUserName;
        String dpsUserPasswordToUse = this.dpsUserPassword;
        String producerIdToUse = this.producerId;
        String materialFlowIdToUse = this.materialFlowId;
        String ieEntityTypeToUse = null;
        if (Boolean.parseBoolean((String) attributes.get("customDepositForm_customFormPopulated"))) {
            /*
             * This is an HTML Serial Deposit harvest. So do not use the 
             * default values, but use those entered by the user
             */
            dpsUserNameToUse = (String) attributes.get("customDepositForm_producerAgent");
            dpsUserPasswordToUse = (String) attributes.get("customDepositForm_producerAgentPassword");
            producerIdToUse = (String) attributes.get("customDepositForm_producerId");
            String targetDcType = (String) attributes.get("customDepositForm_targetDcType");
            materialFlowIdToUse = getMaterialFlowOfTargetDCType(targetDcType);
            ieEntityTypeToUse = getIeEntityTypeOfTargetDCType(targetDcType);

            // If the custom deposit form is filled, this is an HTML Serial harvest.
            parameterMap.put(DpsDepositFacade.HARVEST_TYPE, DpsDepositFacade.HarvestType.HtmlSerialHarvest.name());

            // Capture Dublin Core values in the parameters map
            parameterMap.put(DpsDepositFacade.DCTERMS_BIBLIOGRAPHIC_CITATION,
                    (String) attributes.get("customDepositForm_bibliographicCitation"));
            //            parameterMap.put(DpsDepositFacade.DCTERMS_ACCRUAL_PERIODICITY, (String) attributes.get("customDepositForm_dctermsAccrualPeriodicity"));
            parameterMap.put(DpsDepositFacade.DCTERMS_AVAILABLE,
                    (String) attributes.get("customDepositForm_dctermsAvailable"));
            //            parameterMap.put(DpsDepositFacade.DCTERMS_ISSUED, (String) attributes.get("customDepositForm_dctermsIssued"));
        } else {
            // The custom deposit form is not filled; So this is a traditional web harvest.
            parameterMap.put(DpsDepositFacade.HARVEST_TYPE,
                    DpsDepositFacade.HarvestType.TraditionalWebHarvest.name());
        }
        parameterMap.put(DpsDepositFacade.DPS_INSTITUTION, this.dpsUserInstitution);
        parameterMap.put(DpsDepositFacade.DPS_USER_NAME, dpsUserNameToUse);
        parameterMap.put(DpsDepositFacade.DPS_PASSWORD, dpsUserPasswordToUse);
        parameterMap.put(DpsDepositFacade.FTP_HOST, this.ftpHost);
        parameterMap.put(DpsDepositFacade.FTP_PASSWORD, this.ftpPassword);
        parameterMap.put(DpsDepositFacade.FTP_USER_NAME, this.ftpUserName);
        parameterMap.put(DpsDepositFacade.FTP_DIRECTORY, this.ftpDirectory);
        parameterMap.put(DpsDepositFacade.MATERIAL_FLOW_ID, materialFlowIdToUse);
        parameterMap.put(DpsDepositFacade.IE_ENTITY_TYPE, ieEntityTypeToUse);
        parameterMap.put(DpsDepositFacade.PDS_URL, this.pdsUrl);
        parameterMap.put(DpsDepositFacade.PRODUCER_ID, producerIdToUse);
        parameterMap.put(DpsDepositFacade.DPS_WSDL_URL, depositServerBaseUrl + depositWsdlRelativePath);
        parameterMap.put(DpsDepositFacade.OMS_OPEN_ACCESS, this.omsOpenAccess);
        parameterMap.put(DpsDepositFacade.OMS_PUBLISHED_RESTRICTED, this.omsPublishedRestricted);
        parameterMap.put(DpsDepositFacade.OMS_UNPUBLISHED_RESTRICTED_BY_LOCATION,
                this.omsUnpublishedRestrictedByLocation);
        parameterMap.put(DpsDepositFacade.OMS_UNPUBLISHED_RESTRICTED_BY_PERSON,
                this.omsUnpublishedRestrictedByPersion);

        /*
         * Add target reference number.
         * 
         * The target reference number needs to be taken from the xAttributes map
         * and not from the WCT METS document. A bug in the WCT causes the METS
         * to not contain the target reference number if it was entered into the
         * target after a given target instance is endorsed. 
         */
        String ilsReference = (String) attributes.get(REFERENCE_NUMBER);
        if (ilsReference != null)
            ilsReference = ilsReference.trim();
        parameterMap.put(DpsDepositFacade.ILS_REFERENCE, ilsReference);

        /*
         * Add OMS-style access restriction string
         */
        parameterMap.put(DpsDepositFacade.ACCESS_RESTRICTION, (String) attributes.get(ACCESS_RESTRICTION));

        /*
         * Add the WCT METS XML string
         */
        parameterMap.put(DpsDepositFacade.WCT_METS_XML_DOCUMENT, finalSIP);

        /*
         * Add target instance id
         */
        parameterMap.put(DpsDepositFacade.TARGET_INSTANCE_ID, targetInstanceOID);

        return parameterMap;
    }

    private static class DepDataComparator implements Comparator<DepData> {
        @Override
        public int compare(DepData o1, DepData o2) {
            if (isEmpty(o1) || isEmpty(o2))
                return 0;
            if (isEmpty(o1))
                return 1;
            if (isEmpty(o2))
                return -1;
            return o1.description.compareToIgnoreCase(o2.description);
        }

        private boolean isEmpty(DepData o) {
            if (o == null || o.description == null)
                return true;
            return false;
        }

    }
}