org.kuali.coeus.propdev.impl.budget.subaward.PropDevPropDevBudgetSubAwardServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.coeus.propdev.impl.budget.subaward.PropDevPropDevBudgetSubAwardServiceImpl.java

Source

/*
 * Kuali Coeus, a comprehensive research administration system for higher education.
 * 
 * Copyright 2005-2015 Kuali, Inc.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.coeus.propdev.impl.budget.subaward;

import com.lowagie.text.pdf.*;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xpath.XPathAPI;
import org.kuali.coeus.propdev.impl.budget.ProposalDevelopmentBudgetExt;
import org.kuali.coeus.s2sgen.api.core.InfastructureConstants;
import org.kuali.coeus.s2sgen.api.hash.GrantApplicationHashService;
import org.kuali.coeus.sys.api.model.ScaleTwoDecimal;
import org.kuali.coeus.common.api.sponsor.hierarchy.SponsorHierarchyService;
import org.kuali.coeus.common.budget.framework.core.Budget;
import org.kuali.coeus.common.budget.framework.core.BudgetService;
import org.kuali.coeus.common.budget.framework.nonpersonnel.BudgetLineItem;
import org.kuali.coeus.common.budget.framework.period.BudgetPeriod;
import org.kuali.coeus.sys.framework.gv.GlobalVariableService;
import org.kuali.kra.infrastructure.Constants;
import org.kuali.coeus.s2sgen.api.generate.FormMappingInfo;
import org.kuali.coeus.s2sgen.api.generate.FormMappingService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.data.DataObjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.w3c.dom.*;

import javax.xml.transform.TransformerException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

@Component("propDevBudgetSubAwardService")
public class PropDevPropDevBudgetSubAwardServiceImpl implements PropDevBudgetSubAwardService {
    private static final String DUPLICATE_FILE_NAMES = "Duplicate PDF Attachment File Names";
    private static final String XFA_NS = "http://www.xfa.org/schema/xfa-data/1.0/";
    private static final Log LOG = LogFactory.getLog(PropDevPropDevBudgetSubAwardServiceImpl.class);

    @Autowired
    @Qualifier("parameterService")
    private ParameterService parameterService;

    @Autowired
    @Qualifier("proposalBudgetService")
    private BudgetService budgetService;

    @Autowired
    @Qualifier("dataObjectService")
    private DataObjectService dataObjectService;

    @Autowired
    @Qualifier("dateTimeService")
    private DateTimeService dateTimeService;

    @Autowired
    @Qualifier("formMappingService")
    private FormMappingService formMappingService;

    @Autowired
    @Qualifier("grantApplicationHashService")
    private GrantApplicationHashService grantApplicationHashService;

    @Autowired
    @Qualifier("globalVariableService")
    private GlobalVariableService globalVariableService;

    @Autowired
    @Qualifier("sponsorHierarchyService")
    private SponsorHierarchyService sponsorHierarchyService;

    public void populateBudgetSubAwardFiles(Budget budget, BudgetSubAwards subAward, String newFileName,
            byte[] newFileData) {
        subAward.setSubAwardStatusCode(1);
        BudgetSubAwardFiles newSubAwardFile = new BudgetSubAwardFiles();
        newSubAwardFile.setSubAwardXfdFileData(newFileData);
        subAward.getBudgetSubAwardAttachments().clear();
        subAward.getBudgetSubAwardFiles().clear();
        subAward.getBudgetSubAwardFiles().add(newSubAwardFile);

        boolean subawardBudgetExtracted = false;

        try {
            byte[] pdfFileContents = newSubAwardFile.getSubAwardXfdFileData();
            subAward.setSubAwardXfdFileData(pdfFileContents);
            PdfReader reader = new PdfReader(pdfFileContents);
            byte[] xmlContents = getXMLFromPDF(reader);
            subawardBudgetExtracted = (xmlContents != null && xmlContents.length > 0);
            if (subawardBudgetExtracted) {
                Map fileMap = extractAttachments(reader);
                updateXML(xmlContents, fileMap, subAward, budget);
            }
        } catch (Exception e) {
            LOG.error("Not able to extract xml from pdf", e);
            subawardBudgetExtracted = false;
        }

        newSubAwardFile.setSubAwardXfdFileData(subAward.getSubAwardXfdFileData());
        if (subawardBudgetExtracted) {
            newSubAwardFile.setSubAwardXmlFileData(subAward.getSubAwardXmlFileData());
        }
        newSubAwardFile.setSubAwardXfdFileName(newFileName);
        newSubAwardFile.setBudgetSubAward(subAward);
        subAward.setSubAwardXfdFileName(newFileName);
        subAward.setXfdUpdateUser(getLoggedInUserNetworkId());
        subAward.setXfdUpdateTimestamp(dateTimeService.getCurrentTimestamp());
        subAward.setXmlUpdateUser(getLoggedInUserNetworkId());
        subAward.setXmlUpdateTimestamp(dateTimeService.getCurrentTimestamp());
    }

    public void removeSubAwardAttachment(BudgetSubAwards subAward) {
        subAward.setFormName(null);
        subAward.setNamespace(null);
        subAward.setSubAwardXfdFileData(null);
        subAward.setSubAwardXfdFileName(null);
        subAward.setSubAwardXmlFileData(null);
        subAward.setXfdUpdateUser(null);
        subAward.getBudgetSubAwardAttachments().clear();
        subAward.getBudgetSubAwardFiles().clear();
        subAward.setXfdUpdateUser(getLoggedInUserNetworkId());
        subAward.setXfdUpdateTimestamp(dateTimeService.getCurrentTimestamp());
        subAward.setXmlUpdateUser(getLoggedInUserNetworkId());
        subAward.setXmlUpdateTimestamp(dateTimeService.getCurrentTimestamp());
        resetSubAwardPeriodDetails(subAward);
    }

    protected void resetSubAwardPeriodDetails(BudgetSubAwards subAward) {
        for (BudgetSubAwardPeriodDetail budgetSubAwardPeriodDetail : subAward.getBudgetSubAwardPeriodDetails()) {
            budgetSubAwardPeriodDetail.setDirectCost(ScaleTwoDecimal.ZERO);
            budgetSubAwardPeriodDetail.setCostShare(ScaleTwoDecimal.ZERO);
            budgetSubAwardPeriodDetail.setIndirectCost(ScaleTwoDecimal.ZERO);
            budgetSubAwardPeriodDetail.setTotalCost(ScaleTwoDecimal.ZERO);
        }
    }

    public void prepareBudgetSubAwards(Budget budget) {
        populateBudgetSubAwardAttachments(budget);
        for (BudgetSubAwards subAward : budget.getBudgetSubAwards()) {
            for (BudgetPeriod period : budget.getBudgetPeriods()) {
                BudgetSubAwardPeriodDetail detail = null;
                for (BudgetSubAwardPeriodDetail curDetail : subAward.getBudgetSubAwardPeriodDetails()) {
                    if (ObjectUtils.equals(curDetail.getBudgetPeriod(), period.getBudgetPeriod())) {
                        detail = curDetail;
                        break;
                    }
                }
                if (detail == null) {
                    subAward.getBudgetSubAwardPeriodDetails().add(new BudgetSubAwardPeriodDetail(subAward, period));
                }
            }
        }
    }

    public void generateSubAwardLineItems(BudgetSubAwards subAward, ProposalDevelopmentBudgetExt budget) {
        ScaleTwoDecimal amountChargeFA = new ScaleTwoDecimal(25000);
        boolean isNihProposal = getSponsorHierarchyService()
                .isSponsorNihMultiplePi(budget.getDevelopmentProposal().getSponsorCode());
        String directLtCostElement = getParameterService().getParameterValueAsString(Budget.class,
                Constants.SUBCONTRACTOR_DIRECT_LT_25K_PARAM);
        String directGtCostElement = getParameterService().getParameterValueAsString(Budget.class,
                Constants.SUBCONTRACTOR_DIRECT_GT_25K_PARAM);
        String inDirectLtCostElement = getParameterService().getParameterValueAsString(Budget.class,
                Constants.SUBCONTRACTOR_F_AND_A_LT_25K_PARAM);
        String inDirectGtCostElement = getParameterService().getParameterValueAsString(Budget.class,
                Constants.SUBCONTRACTOR_F_AND_A_GT_25K_PARAM);
        for (BudgetSubAwardPeriodDetail detail : subAward.getBudgetSubAwardPeriodDetails()) {
            BudgetPeriod budgetPeriod = findBudgetPeriod(detail, budget);
            List<BudgetLineItem> currentLineItems = findSubAwardLineItems(budgetPeriod,
                    subAward.getSubAwardNumber());
            //zero out existing line items before recalculating
            for (BudgetLineItem item : currentLineItems) {
                item.setLineItemCost(ScaleTwoDecimal.ZERO);
                item.setDirectCost(ScaleTwoDecimal.ZERO);
                item.setCostSharingAmount(ScaleTwoDecimal.ZERO);
                item.setBudgetSubAward(subAward);
                item.setLineItemDescription(subAward.getOrganizationName());
            }
            ScaleTwoDecimal directCost = ScaleTwoDecimal.returnZeroIfNull(detail.getDirectCost());
            //we only create separate line items for indirect if the proposal is nih
            if (!isNihProposal) {
                directCost = directCost.add(ScaleTwoDecimal.returnZeroIfNull(detail.getIndirectCost()));
            }
            if (directCost.isNonZero()) {
                ScaleTwoDecimal ltValue = lesserValue(directCost, amountChargeFA);
                ScaleTwoDecimal gtValue = directCost.subtract(ltValue);
                if (ltValue.isNonZero()) {
                    BudgetLineItem lt = findOrCreateLineItem(currentLineItems, detail, subAward, budgetPeriod,
                            directLtCostElement);
                    lt.setLineItemCost(ltValue);
                }
                if (gtValue.isNonZero()) {
                    BudgetLineItem gt = findOrCreateLineItem(currentLineItems, detail, subAward, budgetPeriod,
                            directGtCostElement);
                    gt.setLineItemCost(gtValue);
                }
                amountChargeFA = amountChargeFA.subtract(ltValue);
            }
            if (ScaleTwoDecimal.returnZeroIfNull(detail.getIndirectCost()).isNonZero() && isNihProposal) {
                ScaleTwoDecimal ltValue = lesserValue(detail.getIndirectCost(), amountChargeFA);
                ScaleTwoDecimal gtValue = detail.getIndirectCost().subtract(ltValue);
                if (ltValue.isNonZero()) {
                    BudgetLineItem lt = findOrCreateLineItem(currentLineItems, detail, subAward, budgetPeriod,
                            inDirectLtCostElement);
                    lt.setLineItemCost(ltValue);
                }
                if (gtValue.isNonZero()) {
                    BudgetLineItem gt = findOrCreateLineItem(currentLineItems, detail, subAward, budgetPeriod,
                            inDirectGtCostElement);
                    gt.setLineItemCost(gtValue);
                }
                amountChargeFA = amountChargeFA.subtract(ltValue);
            }
            Collections.sort(currentLineItems, new Comparator<BudgetLineItem>() {
                public int compare(BudgetLineItem arg0, BudgetLineItem arg1) {
                    return arg0.getLineItemNumber().compareTo(arg1.getLineItemNumber());
                }
            });
            Iterator<BudgetLineItem> iter = currentLineItems.iterator();
            while (iter.hasNext()) {
                BudgetLineItem lineItem = iter.next();
                if (ScaleTwoDecimal.returnZeroIfNull(lineItem.getLineItemCost()).isZero()) {
                    budgetPeriod.getBudgetLineItems().remove(lineItem);
                    iter.remove();
                } else {
                    if (!budgetPeriod.getBudgetLineItems().contains(lineItem)) {
                        budgetPeriod.getBudgetLineItems().add(lineItem);
                    }
                }
            }
            if (!currentLineItems.isEmpty()
                    && ScaleTwoDecimal.returnZeroIfNull(detail.getCostShare()).isNonZero()) {
                currentLineItems.get(0).setCostSharingAmount(detail.getCostShare());
            }
        }
    }

    protected BudgetPeriod findBudgetPeriod(BudgetSubAwardPeriodDetail detail, Budget budget) {
        for (BudgetPeriod period : budget.getBudgetPeriods()) {
            if (ObjectUtils.equals(detail.getBudgetPeriod(), period.getBudgetPeriod())) {
                return period;
            }
        }
        return null;
    }

    protected ScaleTwoDecimal lesserValue(ScaleTwoDecimal num1, ScaleTwoDecimal num2) {
        if (num1.isLessThan(num2)) {
            return num1;
        } else {
            return num2;
        }
    }

    protected BudgetLineItem findOrCreateLineItem(List<BudgetLineItem> lineItems,
            BudgetSubAwardPeriodDetail subAwardDetail, BudgetSubAwards subAward, BudgetPeriod budgetPeriod,
            String costElement) {
        for (BudgetLineItem curLineItem : lineItems) {
            if (StringUtils.equals(curLineItem.getCostElement(), costElement)) {
                return curLineItem;
            }
        }

        //if we didn't find one already
        BudgetLineItem newLineItem = new BudgetLineItem();
        newLineItem.setCostElement(costElement);
        newLineItem.setBudgetSubAward(subAwardDetail.getBudgetSubAward());
        newLineItem.setLineItemDescription(subAward.getOrganizationName());
        getBudgetService().populateNewBudgetLineItem(newLineItem, budgetPeriod);
        lineItems.add(newLineItem);
        return newLineItem;
    }

    protected List<BudgetLineItem> findSubAwardLineItems(BudgetPeriod budgetPeriod, Integer subAwardNumber) {
        List<BudgetLineItem> lineItems = new ArrayList<BudgetLineItem>();
        if (budgetPeriod.getBudgetLineItems() != null) {
            for (BudgetLineItem item : budgetPeriod.getBudgetLineItems()) {
                if (item.getBudgetSubAward() != null
                        && ObjectUtils.equals(item.getBudgetSubAward().getSubAwardNumber(), subAwardNumber)) {
                    lineItems.add(item);
                }
            }
        }
        return lineItems;
    }

    /**
     * This method return loggedin user id
     */
    protected String getLoggedInUserNetworkId() {
        return globalVariableService.getUserSession().getPrincipalName();
    }

    /**
     * extracts XML from PDF
     */
    protected byte[] getXMLFromPDF(PdfReader reader) throws Exception {
        XfaForm xfaForm = reader.getAcroFields().getXfa();
        Node domDocument = xfaForm.getDomDocument();
        if (domDocument == null)
            return null;
        Element documentElement = ((Document) domDocument).getDocumentElement();

        Element datasetsElement = (Element) documentElement.getElementsByTagNameNS(XFA_NS, "datasets").item(0);
        Element dataElement = (Element) datasetsElement.getElementsByTagNameNS(XFA_NS, "data").item(0);

        Element xmlElement = (Element) dataElement.getChildNodes().item(0);

        Node budgetElement = getBudgetElement(xmlElement);

        byte[] serializedXML = XfaForm.serializeDoc(budgetElement);

        return serializedXML;
    }

    private Node getBudgetElement(Element xmlElement) throws Exception {
        Node budgetNode = (Node) xmlElement;
        NodeList budgetAttachments = XPathAPI.selectNodeList(xmlElement,
                "//*[local-name(.) = 'BudgetAttachments']");
        if (budgetAttachments != null && budgetAttachments.getLength() > 0) {
            Element budgetAttachment = (Element) budgetAttachments.item(0);
            if (budgetAttachment.hasChildNodes()) {
                budgetNode = budgetAttachment.getFirstChild();
            }
        }
        return budgetNode;
    }

    /**
     * extracts attachments from PDF File
     */

    @SuppressWarnings("unchecked")
    protected Map extractAttachments(PdfReader reader) throws IOException {
        Map fileMap = new HashMap();
        PdfDictionary catalog = reader.getCatalog();
        PdfDictionary names = (PdfDictionary) PdfReader.getPdfObject(catalog.get(PdfName.NAMES));
        if (names != null) {
            PdfDictionary embFiles = (PdfDictionary) PdfReader
                    .getPdfObject(names.get(new PdfName("EmbeddedFiles")));
            if (embFiles != null) {
                HashMap embMap = PdfNameTree.readTree(embFiles);
                for (Iterator i = embMap.values().iterator(); i.hasNext();) {
                    PdfDictionary filespec = (PdfDictionary) PdfReader.getPdfObject((PdfObject) i.next());
                    Object fileInfo[] = unpackFile(reader, filespec);
                    if (fileMap.containsKey(fileInfo[0])) {
                        throw new RuntimeException(DUPLICATE_FILE_NAMES);
                    }
                    fileMap.put(fileInfo[0], fileInfo[1]);
                }
            }
        }
        for (int k = 1; k <= reader.getNumberOfPages(); ++k) {
            PdfArray annots = (PdfArray) PdfReader.getPdfObject(reader.getPageN(k).get(PdfName.ANNOTS));
            if (annots == null) {
                continue;
            }
            for (Iterator i = annots.getArrayList().listIterator(); i.hasNext();) {
                PdfDictionary annot = (PdfDictionary) PdfReader.getPdfObject((PdfObject) i.next());
                PdfName subType = (PdfName) PdfReader.getPdfObject(annot.get(PdfName.SUBTYPE));
                if (!PdfName.FILEATTACHMENT.equals(subType)) {
                    continue;
                }
                PdfDictionary filespec = (PdfDictionary) PdfReader.getPdfObject(annot.get(PdfName.FS));
                Object fileInfo[] = unpackFile(reader, filespec);
                if (fileMap.containsKey(fileInfo[0])) {
                    throw new RuntimeException(DUPLICATE_FILE_NAMES);
                }

                fileMap.put(fileInfo[0], fileInfo[1]);
            }
        }

        return fileMap;
    }

    /**
     * Unpacks a file attachment.
     * @param reader The object that reads the PDF document
     * @param filespec The dictonary containing the file specifications
     * @throws IOException
     */

    protected static Object[] unpackFile(PdfReader reader, PdfDictionary filespec) throws IOException {
        Object arr[] = new Object[2]; //use to store name and file bytes
        if (filespec == null) {
            return null;
        }

        PdfName type = (PdfName) PdfReader.getPdfObject(filespec.get(PdfName.TYPE));
        if (!PdfName.F.equals(type) && !PdfName.FILESPEC.equals(type)) {
            return null;
        }

        PdfDictionary ef = (PdfDictionary) PdfReader.getPdfObject(filespec.get(PdfName.EF));
        if (ef == null) {
            return null;
        }

        PdfString fn = (PdfString) PdfReader.getPdfObject(filespec.get(PdfName.F));
        if (fn == null) {
            return null;
        }

        File fLast = new File(fn.toUnicodeString());
        PRStream prs = (PRStream) PdfReader.getPdfObject(ef.get(PdfName.F));
        if (prs == null) {
            return null;
        }

        byte attachmentByte[] = PdfReader.getStreamBytes(prs);
        arr[0] = fLast.getName();
        arr[1] = attachmentByte;

        return arr;

    }

    @Override
    public boolean updateSubAwardBudgetDetails(Budget budget, BudgetSubAwards budgetSubAward, List<String[]> errors)
            throws Exception {
        boolean result = true;

        //extarct xml from the pdf because the stored xml has been modified
        if (budgetSubAward.getSubAwardXfdFileData() == null
                || budgetSubAward.getSubAwardXfdFileData().length == 0) {
            errors.add(new String[] { Constants.SUBAWARD_FILE_NOT_EXTRACTED });
            return true;
        }
        PdfReader reader = new PdfReader(budgetSubAward.getSubAwardXfdFileData());
        byte[] xmlContents = getXMLFromPDF(reader);
        if (xmlContents == null) {
            errors.add(new String[] { Constants.SUBAWARD_FILE_NOT_EXTRACTED });
            return true;
        }
        javax.xml.parsers.DocumentBuilderFactory domParserFactory = javax.xml.parsers.DocumentBuilderFactory
                .newInstance();
        javax.xml.parsers.DocumentBuilder domParser = domParserFactory.newDocumentBuilder();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xmlContents);
        org.w3c.dom.Document document = domParser.parse(byteArrayInputStream);
        NodeList budgetYearList = XPathAPI.selectNodeList(document, "//*[local-name(.) = 'BudgetYear']");
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        boolean fnfForm = StringUtils.contains(budgetSubAward.getFormName(), "RR_FedNonFedBudget");

        //reset current line items if replacing with a new one.
        resetSubAwardPeriodDetails(budgetSubAward);

        for (int i = 0; i < budgetYearList.getLength(); i++) {
            Node budgetYear = budgetYearList.item(i);
            Node startDateNode = XPathAPI.selectSingleNode(budgetYear, "BudgetPeriodStartDate");
            if (startDateNode == null) {
                startDateNode = XPathAPI.selectSingleNode(budgetYear, "PeriodStartDate");
            }
            Node endDateNode = XPathAPI.selectSingleNode(budgetYear, "BudgetPeriodEndDate");
            if (endDateNode == null) {
                endDateNode = XPathAPI.selectSingleNode(budgetYear, "PeriodEndDate");
            }
            Date startDate = dateFormat.parse(startDateNode.getTextContent());
            Date endDate = dateFormat.parse(endDateNode.getTextContent());
            //attempt to find a matching budget period
            BudgetSubAwardPeriodDetail periodDetail = findBudgetSubAwardPeriodDetail(budget, budgetSubAward,
                    startDate, endDate);
            if (periodDetail != null) {
                Node directCostNode, indirectCostNode, costShareNode = null;
                if (fnfForm) {
                    directCostNode = XPathAPI.selectSingleNode(budgetYear, "DirectCosts/FederalSummary");
                    indirectCostNode = XPathAPI.selectSingleNode(budgetYear,
                            "IndirectCosts/TotalIndirectCosts/FederalSummary");
                    costShareNode = XPathAPI.selectSingleNode(budgetYear, "TotalCosts/NonFederalSummary");
                } else {
                    directCostNode = XPathAPI.selectSingleNode(budgetYear, "DirectCosts");
                    indirectCostNode = XPathAPI.selectSingleNode(budgetYear, "IndirectCosts/TotalIndirectCosts");
                }
                if (directCostNode != null) {
                    periodDetail
                            .setDirectCost(new ScaleTwoDecimal(Float.parseFloat(directCostNode.getTextContent())));
                }
                if (indirectCostNode != null) {
                    periodDetail.setIndirectCost(
                            new ScaleTwoDecimal(Float.parseFloat(indirectCostNode.getTextContent())));
                }
                if (costShareNode != null) {
                    periodDetail
                            .setCostShare(new ScaleTwoDecimal(Float.parseFloat(costShareNode.getTextContent())));
                } else {
                    periodDetail.setCostShare(ScaleTwoDecimal.ZERO);
                }
                periodDetail.computeTotal();
            } else {
                Node budgetPeriodNode = XPathAPI.selectSingleNode(budgetYear, "BudgetPeriod");
                String budgetPeriod = null;
                if (budgetPeriodNode != null) {
                    budgetPeriod = budgetPeriodNode.getTextContent();
                }
                LOG.debug("Unable to find matching period for uploaded period '" + budgetPeriod + "' -- "
                        + startDateNode.getTextContent() + " - " + endDateNode.getTextContent());
                errors.add(new String[] { Constants.SUBAWARD_FILE_PERIOD_NOT_FOUND,
                        (budgetPeriod == null ? "" : budgetPeriod), startDateNode.getTextContent(),
                        endDateNode.getTextContent() });
            }
        }
        return result;
    }

    /**
     * First find a budget period that matches the start and end date. If that is found, find a subaward period detail with the same
     * budget period number.
     * @param budget
     * @param budgetSubAward
     * @param startDate
     * @param endDate
     * @return
     */
    protected BudgetSubAwardPeriodDetail findBudgetSubAwardPeriodDetail(Budget budget,
            BudgetSubAwards budgetSubAward, Date startDate, Date endDate) {
        BudgetPeriod matchingPeriod = null;
        BudgetSubAwardPeriodDetail matchingDetail = null;
        for (BudgetPeriod period : budget.getBudgetPeriods()) {
            if (startDate.getTime() == period.getStartDate().getTime()
                    && endDate.getTime() == period.getEndDate().getTime()) {
                matchingPeriod = period;
                break;
            }
        }
        if (matchingPeriod != null) {

            for (BudgetSubAwardPeriodDetail detail : budgetSubAward.getBudgetSubAwardPeriodDetails()) {
                if (ObjectUtils.equals(detail.getBudgetPeriod(), matchingPeriod.getBudgetPeriod())) {
                    matchingDetail = detail;
                    break;
                }
            }
        }
        return matchingDetail;
    }

    /**
     * updates the XMl with hashcode for the files
     */

    protected BudgetSubAwards updateXML(byte xmlContents[], Map fileMap, BudgetSubAwards budgetSubAwardBean,
            Budget budget) throws Exception {

        javax.xml.parsers.DocumentBuilderFactory domParserFactory = javax.xml.parsers.DocumentBuilderFactory
                .newInstance();
        javax.xml.parsers.DocumentBuilder domParser = domParserFactory.newDocumentBuilder();
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xmlContents);

        org.w3c.dom.Document document = domParser.parse(byteArrayInputStream);
        byteArrayInputStream.close();
        String namespace = null;
        String formName = null;
        if (document != null) {
            Node node;
            Element element = document.getDocumentElement();
            NamedNodeMap map = element.getAttributes();
            String namespaceHolder = element.getNodeName().substring(0, element.getNodeName().indexOf(':'));
            node = map.getNamedItem("xmlns:" + namespaceHolder);
            namespace = node.getNodeValue();
            FormMappingInfo formMappingInfo = formMappingService.getFormInfo(namespace);
            formName = formMappingInfo.getFormName();
            budgetSubAwardBean.setNamespace(namespace);
            budgetSubAwardBean.setFormName(formName);
        }

        String xpathEmptyNodes = "//*[not(node()) and local-name(.) != 'FileLocation' and local-name(.) != 'HashValue']";
        String xpathOtherPers = "//*[local-name(.)='ProjectRole' and local-name(../../.)='OtherPersonnel' and count(../NumberOfPersonnel)=0]";
        removeAllEmptyNodes(document, xpathEmptyNodes, 0);
        removeAllEmptyNodes(document, xpathOtherPers, 1);
        removeAllEmptyNodes(document, xpathEmptyNodes, 0);
        changeDataTypeForNumberOfOtherPersons(document);

        List<String> fedNonFedSubAwardForms = getFedNonFedSubawardForms();
        NodeList budgetYearList = XPathAPI.selectNodeList(document, "//*[local-name(.) = 'BudgetYear']");
        for (int i = 0; i < budgetYearList.getLength(); i++) {
            Node bgtYearNode = budgetYearList.item(i);
            String period = getValue(XPathAPI.selectSingleNode(bgtYearNode, "BudgetPeriod"));
            if (fedNonFedSubAwardForms.contains(namespace)) {
                Element newBudgetYearElement = copyElementToName((Element) bgtYearNode, bgtYearNode.getNodeName());
                bgtYearNode.getParentNode().replaceChild(newBudgetYearElement, bgtYearNode);
            } else {
                Element newBudgetYearElement = copyElementToName((Element) bgtYearNode,
                        bgtYearNode.getNodeName() + period);
                bgtYearNode.getParentNode().replaceChild(newBudgetYearElement, bgtYearNode);
            }
        }

        Node oldroot = document.removeChild(document.getDocumentElement());
        Node newroot = document.appendChild(document.createElement("Forms"));
        newroot.appendChild(oldroot);

        org.w3c.dom.NodeList lstFileName = document.getElementsByTagName("att:FileName");
        org.w3c.dom.NodeList lstFileLocation = document.getElementsByTagName("att:FileLocation");
        org.w3c.dom.NodeList lstMimeType = document.getElementsByTagName("att:MimeType");
        org.w3c.dom.NodeList lstHashValue = document.getElementsByTagName("glob:HashValue");

        org.w3c.dom.Node fileNode, hashNode, mimeTypeNode;
        org.w3c.dom.NamedNodeMap fileNodeMap, hashNodeMap;
        String fileName;
        byte fileBytes[];
        String contentId;
        List attachmentList = new ArrayList();

        for (int index = 0; index < lstFileName.getLength(); index++) {
            fileNode = lstFileName.item(index);

            Node fileNameNode = fileNode.getFirstChild();
            fileName = fileNameNode.getNodeValue();

            fileBytes = (byte[]) fileMap.get(fileName);

            if (fileBytes == null) {
                throw new RuntimeException("FileName mismatch in XML and PDF extracted file");
            }
            String hashVal = grantApplicationHashService.computeAttachmentHash(fileBytes);

            hashNode = lstHashValue.item(index);
            hashNodeMap = hashNode.getAttributes();

            Node temp = document.createTextNode(hashVal);
            hashNode.appendChild(temp);

            hashNode = hashNodeMap.getNamedItem("glob:hashAlgorithm");

            hashNode.setNodeValue(InfastructureConstants.HASH_ALGORITHM);

            fileNode = lstFileLocation.item(index);
            fileNodeMap = fileNode.getAttributes();
            fileNode = fileNodeMap.getNamedItem("att:href");

            contentId = fileNode.getNodeValue();
            String encodedContentId = cleanContentId(contentId);
            fileNode.setNodeValue(encodedContentId);

            mimeTypeNode = lstMimeType.item(0);
            String contentType = mimeTypeNode.getFirstChild().getNodeValue();

            BudgetSubAwardAttachment budgetSubAwardAttachmentBean = new BudgetSubAwardAttachment();
            budgetSubAwardAttachmentBean.setData(fileBytes);
            budgetSubAwardAttachmentBean.setName(encodedContentId);

            budgetSubAwardAttachmentBean.setType(contentType);
            budgetSubAwardAttachmentBean.setBudgetSubAward(budgetSubAwardBean);

            attachmentList.add(budgetSubAwardAttachmentBean);
        }

        budgetSubAwardBean.setBudgetSubAwardAttachments(attachmentList);

        javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory.newInstance()
                .newTransformer();
        transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(bos);
        javax.xml.transform.dom.DOMSource source = new javax.xml.transform.dom.DOMSource(document);

        transformer.transform(source, result);

        budgetSubAwardBean.setSubAwardXmlFileData(new String(bos.toByteArray()));

        bos.close();

        return budgetSubAwardBean;
    }

    protected String cleanContentId(String contentId) {
        return StringUtils.replaceChars(contentId, " .%-_", "");
    }

    public void populateBudgetSubAwardAttachments(Budget budget) {
        List<BudgetSubAwards> subAwards = budget.getBudgetSubAwards();
        for (BudgetSubAwards budgetSubAwards : subAwards) {
            budgetSubAwards.refreshReferenceObject("budgetSubAwardAttachments");
        }
    }

    protected void removeAllEmptyNodes(Document document, String xpath, int parentLevel)
            throws TransformerException {
        NodeList emptyElements = XPathAPI.selectNodeList(document, xpath);

        for (int i = emptyElements.getLength() - 1; i > -1; i--) {
            Node nodeToBeRemoved = emptyElements.item(i);
            int hierLevel = parentLevel;
            while (hierLevel-- > 0) {
                nodeToBeRemoved = nodeToBeRemoved.getParentNode();
            }
            nodeToBeRemoved.getParentNode().removeChild(nodeToBeRemoved);
        }
        NodeList moreEmptyElements = XPathAPI.selectNodeList(document, xpath);
        if (moreEmptyElements.getLength() > 0) {
            removeAllEmptyNodes(document, xpath, parentLevel);
        }
    }

    protected Element copyElementToName(Element element, String tagName) {
        Element newElement = element.getOwnerDocument().createElement(tagName);
        NamedNodeMap attrs = element.getAttributes();
        for (int i = 0; i < attrs.getLength(); i++) {
            Node attribute = attrs.item(i);
            newElement.setAttribute(attribute.getNodeName(), attribute.getNodeValue());
        }
        for (int i = 0; i < element.getChildNodes().getLength(); i++) {
            newElement.appendChild(element.getChildNodes().item(i).cloneNode(true));
        }
        return newElement;
    }

    private void changeDataTypeForNumberOfOtherPersons(Document document) throws Exception {
        NodeList otherPesronsCountNodes = XPathAPI.selectNodeList(document,
                "//*[local-name(.)='OtherPersonnelTotalNumber']");
        for (int i = 0; i < otherPesronsCountNodes.getLength(); i++) {
            Node countNode = otherPesronsCountNodes.item(i);
            String value = getValue(countNode);

            if (value != null && value.length() > 0 && value.indexOf('.') != -1) {
                int intVal = Double.valueOf(value).intValue();
                setValue(countNode, "" + intVal);
            }
        }
    }

    private void setValue(Node node, String value) {
        Node child = null;
        for (child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getNodeType() == Node.TEXT_NODE) {
                child.setNodeValue(value);
                break;
            }
        }
    }

    private static String getValue(Node node) {
        String textValue = "";
        Node child = null;
        if (node != null)
            for (child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
                if (child.getNodeType() == Node.TEXT_NODE) {
                    textValue = child.getNodeValue();
                    break;
                }
            }
        return textValue.trim();
    }

    private List<String> getFedNonFedSubawardForms() {
        List<String> forms = new ArrayList<String>();
        forms.add("http://apply.grants.gov/forms/RR_FedNonFedBudget10-V1.1");
        return forms;
    }

    protected ParameterService getParameterService() {
        return parameterService;
    }

    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    protected BudgetService getBudgetService() {
        return budgetService;
    }

    public void setBudgetService(BudgetService budgetService) {
        this.budgetService = budgetService;
    }

    public DateTimeService getDateTimeService() {
        return dateTimeService;
    }

    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    public FormMappingService getFormMappingService() {
        return formMappingService;
    }

    public void setFormMappingService(FormMappingService formMappingService) {
        this.formMappingService = formMappingService;
    }

    public GrantApplicationHashService getGrantApplicationHashService() {
        return grantApplicationHashService;
    }

    public void setGrantApplicationHashService(GrantApplicationHashService grantApplicationHashService) {
        this.grantApplicationHashService = grantApplicationHashService;
    }

    public GlobalVariableService getGlobalVariableService() {
        return globalVariableService;
    }

    public void setGlobalVariableService(GlobalVariableService globalVariableService) {
        this.globalVariableService = globalVariableService;
    }

    public DataObjectService getDataObjectService() {
        return dataObjectService;
    }

    public void setDataObjectService(DataObjectService dataObjectService) {
        this.dataObjectService = dataObjectService;
    }

    public SponsorHierarchyService getSponsorHierarchyService() {
        return sponsorHierarchyService;
    }

    public void setSponsorHierarchyService(SponsorHierarchyService sponsorHierarchyService) {
        this.sponsorHierarchyService = sponsorHierarchyService;
    }
}