Java tutorial
/* * 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.hierarchy; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kuali.coeus.common.budget.framework.personnel.*; import org.kuali.coeus.common.framework.keyword.ScienceKeyword; import org.kuali.coeus.propdev.impl.attachment.*; import org.kuali.coeus.propdev.impl.budget.ProposalBudgetStatus; import org.kuali.coeus.propdev.impl.core.DevelopmentProposal; import org.kuali.coeus.propdev.impl.core.ProposalDevelopmentDocument; import org.kuali.coeus.propdev.impl.keyword.PropScienceKeyword; import org.kuali.coeus.propdev.impl.location.CongressionalDistrict; import org.kuali.coeus.propdev.impl.location.ProposalSite; import org.kuali.coeus.propdev.impl.person.KeyPersonnelService; import org.kuali.coeus.propdev.impl.person.ProposalPerson; import org.kuali.coeus.propdev.impl.person.ProposalPersonDegree; import org.kuali.coeus.propdev.impl.person.ProposalPersonUnit; import org.kuali.coeus.propdev.impl.person.attachment.ProposalPersonBiography; import org.kuali.coeus.common.framework.auth.perm.KcAuthorizationService; import org.kuali.coeus.propdev.impl.person.creditsplit.ProposalPersonCreditSplit; import org.kuali.coeus.propdev.impl.person.creditsplit.ProposalUnitCreditSplit; import org.kuali.coeus.propdev.impl.s2s.S2sOppForms; import org.kuali.coeus.propdev.impl.s2s.S2sOpportunity; import org.kuali.coeus.sys.framework.gv.GlobalVariableService; import org.kuali.coeus.sys.framework.model.KcDataObject; import org.kuali.coeus.sys.framework.workflow.KcDocumentRejectionService; import org.kuali.coeus.sys.api.model.ScaleTwoDecimal; import org.kuali.coeus.common.budget.framework.core.Budget; import org.kuali.coeus.common.budget.framework.period.BudgetPeriod; import org.kuali.kra.infrastructure.Constants; import org.kuali.kra.infrastructure.PermissionConstants; import org.kuali.kra.infrastructure.RoleConstants; import org.kuali.coeus.propdev.impl.budget.hierarchy.ProposalBudgetHierarchyService; import org.kuali.coeus.propdev.impl.budget.ProposalDevelopmentBudgetExt; import org.kuali.coeus.propdev.impl.person.attachment.ProposalPersonBiographyService; import org.kuali.coeus.propdev.impl.specialreview.ProposalSpecialReview; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.criteria.OrderByField; import org.kuali.rice.core.api.criteria.OrderDirection; import org.kuali.rice.core.api.criteria.QueryByCriteria; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.KewApiConstants; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kew.api.WorkflowDocumentFactory; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange; import org.kuali.rice.kim.api.identity.IdentityService; import org.kuali.rice.kns.service.SessionDocumentService; import org.kuali.rice.krad.bo.DocumentHeader; import org.kuali.rice.krad.data.CopyOption; import org.kuali.rice.krad.data.DataObjectService; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.service.PessimisticLockService; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.krad.workflow.service.WorkflowDocumentService; import org.springframework.transaction.annotation.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.persistence.EntityManager; import java.io.IOException; import java.sql.Timestamp; import java.util.*; import static org.apache.commons.lang3.StringUtils.replace; import static org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyKeyConstants.*; @Component("proposalHierarchyService") @Transactional public class ProposalHierarchyServiceImpl implements ProposalHierarchyService { private static final Log LOG = LogFactory.getLog(ProposalHierarchyServiceImpl.class); @Autowired @Qualifier("dataObjectService") private DataObjectService dataObjectService; @Autowired @Qualifier("documentService") private DocumentService documentService; @Autowired @Qualifier("kcAuthorizationService") private KcAuthorizationService kcAuthorizationService; @Autowired @Qualifier("proposalHierarchyDao") private ProposalHierarchyDao proposalHierarchyDao; @Autowired @Qualifier("legacyNarrativeService") private LegacyNarrativeService legacyNarrativeService; @Autowired @Qualifier("proposalPersonBiographyService") private ProposalPersonBiographyService proposalPersonBiographyService; @Autowired @Qualifier("parameterService") private ParameterService parameterService; @Autowired @Qualifier("identityService") private IdentityService identityService; @Autowired @Qualifier("kualiConfigurationService") private ConfigurationService kualiConfigurationService; @Autowired @Qualifier("kcDocumentRejectionService") private KcDocumentRejectionService kcDocumentRejectionService; @Autowired @Qualifier("knsSessionDocumentService") private SessionDocumentService knsSessionDocumentService; @Autowired @Qualifier("kradWorkflowDocumentService") private WorkflowDocumentService kradWorkflowDocumentService; @Autowired @Qualifier("keyPersonnelService") private KeyPersonnelService keyPersonnelService; @Autowired @Qualifier("globalVariableService") private GlobalVariableService globalVariableService; @Autowired @Qualifier("pessimisticLockService") private PessimisticLockService pessimisticLockService; @Autowired @Qualifier("proposalBudgetHierarchyService") private ProposalBudgetHierarchyService proposalBudgetHierarchyService; //Setters for dependency injection public void setIdentityService(IdentityService identityService) { this.identityService = identityService; } public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } public void setKcAuthorizationService(KcAuthorizationService kcAuthorizationService) { this.kcAuthorizationService = kcAuthorizationService; } public void setProposalHierarchyDao(ProposalHierarchyDao proposalHierarchyDao) { this.proposalHierarchyDao = proposalHierarchyDao; } public void setLegacyNarrativeService(LegacyNarrativeService narrativeService) { this.legacyNarrativeService = narrativeService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public void setKualiConfigurationService(ConfigurationService kualiConfigurationService) { this.kualiConfigurationService = kualiConfigurationService; } public String createHierarchy(DevelopmentProposal initialChild, String userId) { LOG.info(String.format("***Create Hierarchy using Proposal #%s", initialChild.getProposalNumber())); ProposalDevelopmentDocument newDoc = assembleDoc(); // copy the initial information to the new parent proposal DevelopmentProposal hierarchy = newDoc.getDevelopmentProposal(); copyInitialData(hierarchy, initialChild); // set hierarchy status setHierarchyStatus(initialChild.getProposalDocument(), newDoc); ProposalDevelopmentDocument hierarchyDoc = saveDocument(newDoc); copyOpportunity(initialChild, hierarchyDoc.getDevelopmentProposal()); // add aggregator to the document kcAuthorizationService.addDocumentLevelRole(userId, RoleConstants.AGGREGATOR, hierarchyDoc); proposalBudgetHierarchyService.initializeBudget(hierarchyDoc.getDevelopmentProposal(), initialChild); //we are creating the attachments for the first time so do not sync personnel attachments at this time, just copy as is. linkChild(hierarchyDoc.getDevelopmentProposal(), initialChild, HierarchyBudgetTypeConstants.SubBudget.code(), false); setInitialPi(hierarchyDoc.getDevelopmentProposal(), initialChild); copyInitialAttachments(initialChild, hierarchyDoc.getDevelopmentProposal()); addCreateDetails(newDoc); LOG.info(String.format("***Initial Child (#%s) linked to Parent (#%s)", initialChild.getProposalNumber(), hierarchyDoc.getDevelopmentProposal().getProposalNumber())); finalizeHierarchySync(hierarchyDoc.getDevelopmentProposal()); // return the parent id LOG.info(String.format("***Hierarchy creation (#%s) complete", hierarchyDoc.getDevelopmentProposal().getProposalNumber())); return hierarchyDoc.getDevelopmentProposal().getProposalNumber(); } private void addCreateDetails(ProposalDevelopmentDocument proposalDevelopmentDocument) { proposalDevelopmentDocument.getDevelopmentProposal() .setCreateTimestamp(new Timestamp(System.currentTimeMillis())); proposalDevelopmentDocument.getDevelopmentProposal() .setCreateUser(getGlobalVariableService().getUserSession().getLoggedInUserPrincipalName()); } protected ProposalDevelopmentDocument saveDocument(ProposalDevelopmentDocument newDoc) { ProposalDevelopmentDocument hierarchyDoc; // persist the document and add a budget try { hierarchyDoc = (ProposalDevelopmentDocument) documentService.saveDocument(newDoc); } catch (WorkflowException x) { throw new ProposalHierarchyException("Error saving new document: " + x); } return hierarchyDoc; } protected void setHierarchyStatus(ProposalDevelopmentDocument childDocument, ProposalDevelopmentDocument newDoc) { DevelopmentProposal hierarchy = newDoc.getDevelopmentProposal(); hierarchy.setHierarchyStatus(HierarchyStatusConstants.Parent.code()); String docDescription = childDocument.getDocumentHeader().getDocumentDescription(); newDoc.getDocumentHeader().setDocumentDescription(docDescription); newDoc.setDevelopmentProposal(hierarchy); hierarchy.setProposalDocument(newDoc); } protected ProposalDevelopmentDocument assembleDoc() { ProposalDevelopmentDocument newDoc; // manually assembling a new PDDoc here because the DocumentService will deny initiator permission without context // since a person with MAINTAIN_PROPOSAL_HIERARCHY permission is allowed to initiate IF they are creating a parent // we circumvent the initiator step altogether. try { WorkflowDocument workflowDocument = kradWorkflowDocumentService.createWorkflowDocument( PROPOSAL_DEVELOPMENT_DOCUMENT_TYPE, globalVariableService.getUserSession().getPerson()); DocumentHeader documentHeader = new DocumentHeader(); documentHeader.setWorkflowDocument(workflowDocument); documentHeader.setDocumentNumber(workflowDocument.getDocumentId().toString()); newDoc = new ProposalDevelopmentDocument(); newDoc.setDocumentHeader(documentHeader); newDoc.setDocumentNumber(documentHeader.getDocumentNumber()); } catch (WorkflowException x) { throw new ProposalHierarchyException("Error creating new document: " + x); } return newDoc; } public void linkToHierarchy(DevelopmentProposal hierarchyProposal, DevelopmentProposal newChildProposal, String hierarchyBudgetTypeCode) throws ProposalHierarchyException { prepareHierarchySync(hierarchyProposal); linkChild(hierarchyProposal, newChildProposal, hierarchyBudgetTypeCode, true); finalizeHierarchySync(hierarchyProposal); } public List<ProposalHierarchyErrorWarningDto> validateLinkToHierarchy(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (hierarchyProposal == null) { errors.add(new ProposalHierarchyErrorWarningDto( ProposalHierarchyKeyConstants.ERROR_PROPOSAL_DOES_NOT_EXIST, Boolean.TRUE, new String[0])); return errors; } else { if (!hierarchyProposal.isParent()) { errors.add(new ProposalHierarchyErrorWarningDto( ProposalHierarchyKeyConstants.ERROR_PROPOSAL_NOT_HIERARCHY_PARENT, Boolean.TRUE, hierarchyProposal.getProposalNumber())); } } if (childProposal.isInHierarchy()) { errors.add(new ProposalHierarchyErrorWarningDto(ProposalHierarchyKeyConstants.ERROR_NOT_HIERARCHY_CHILD, Boolean.TRUE, childProposal.getProposalNumber())); } errors.addAll(validateChildBudgetPeriods(hierarchyProposal, childProposal, true)); errors.addAll(validateSponsor(childProposal, hierarchyProposal)); errors.addAll(validateParent(hierarchyProposal)); errors.addAll(validateIsParentLocked(hierarchyProposal)); errors.addAll(validateIsAggregatorOnParent(hierarchyProposal)); List<ProposalHierarchyErrorWarningDto> sponsorErrors = validateSponsor(childProposal, hierarchyProposal); errors.addAll(sponsorErrors); return errors; } public DevelopmentProposal removeFromHierarchy(DevelopmentProposal childProposal) throws ProposalHierarchyException { String hierarchyProposalNumber = childProposal.getHierarchyParentProposalNumber(); DevelopmentProposal hierarchyProposal = getHierarchy(hierarchyProposalNumber); List<String> childProposalNumbers = proposalHierarchyDao .getHierarchyChildProposalNumbers(hierarchyProposalNumber); boolean isLast = childProposalNumbers.size() == 1 && StringUtils.equals(childProposalNumbers.get(0), childProposal.getProposalNumber()); childProposal.setHierarchyStatus(HierarchyStatusConstants.None.code()); childProposal.setHierarchyParentProposalNumber(null); if (StringUtils.equalsIgnoreCase(hierarchyProposal.getHierarchyOriginatingChildProposalNumber(), childProposal.getProposalNumber())) { hierarchyProposal.getPrincipalInvestigator().setHierarchyProposalNumber(null); } removeChildElements(hierarchyProposal, childProposal.getProposalNumber()); removeAllChildPersonnelFromParent(hierarchyProposal, childProposal); if (isLast) { try { childProposal = dataObjectService.save(childProposal); Document doc = documentService .getByDocumentHeaderId(hierarchyProposal.getProposalDocument().getDocumentNumber()); documentService.cancelDocument(doc, "Removed last child from Proposal Hierarchy"); return childProposal; } catch (WorkflowException e) { throw new ProposalHierarchyException("Error cancelling empty parent proposal"); } } else { //need to find out what the lowest number is //so we can make it the new primary child for budget syncs. String lowestProposalNumber = ""; for (String proposalNumber : childProposalNumbers) { if (!StringUtils.equals(proposalNumber, childProposal.getProposalNumber())) { lowestProposalNumber = proposalNumber; break; } } hierarchyProposal.setHierarchyOriginatingChildProposalNumber(lowestProposalNumber); dataObjectService.save(childProposal); dataObjectService.save(hierarchyProposal); return childProposal; } } protected void removeDeletedPersonnelFromParent(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { for (Iterator<ProposalPerson> iterator = hierarchyProposal.getProposalPersons().iterator(); iterator .hasNext();) { ProposalPerson person = iterator.next(); if (StringUtils.equals(person.getHierarchyProposalNumber(), childProposal.getProposalNumber()) && childProposal.getProposalPersons().indexOf(person) == -1 && !personInMultipleProposals(person.getPersonId(), hierarchyProposal)) { //remove person from parent iterator.remove(); // remove attachments also getProposalPersonBiographyService() .removePersonnelAttachmentForDeletedPerson(hierarchyProposal.getProposalDocument(), person); } } } protected void removeAllChildPersonnelFromParent(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { for (Iterator<ProposalPerson> iterator = childProposal.getProposalPersons().iterator(); iterator .hasNext();) { ProposalPerson childPerson = iterator.next(); if (!personInMultipleProposals(childPerson.getPersonId(), hierarchyProposal)) { for (Iterator<ProposalPerson> parentIterator = hierarchyProposal.getProposalPersons() .iterator(); parentIterator.hasNext();) { ProposalPerson parentPerson = parentIterator.next(); if (StringUtils.equals(childPerson.getPersonId(), parentPerson.getPersonId())) { //remove person from parent parentIterator.remove(); // remove attachments also getProposalPersonBiographyService().removePersonnelAttachmentForDeletedPerson( hierarchyProposal.getProposalDocument(), parentPerson); } } } } } public void synchronizeAllChildren(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException { prepareHierarchySync(hierarchyProposal); synchronizeAll(hierarchyProposal); DevelopmentProposal savedParent = finalizeHierarchySync(hierarchyProposal); // because refresh reference does not work, having to retrieve and add to proposal // so it displays right. reinstateCollections(hierarchyProposal); } protected void reinstateCollections(DevelopmentProposal proposal) { reinstateDegreeInfo(proposal); } @Override public DevelopmentProposal getDevelopmentProposal(String proposalNumber) { return getProposalHierarchyDao().getDevelopmentProposal(proposalNumber); } public String getProposalState(String proposalNumber) { return getProposalHierarchyDao().getProposalState(proposalNumber); } protected void synchronizeAll(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException { synchronizeAllChildProposals(hierarchyProposal); } @Override public void synchronizeChild(DevelopmentProposal childProposal) throws ProposalHierarchyException { DevelopmentProposal hierarchy = getHierarchy(childProposal.getHierarchyParentProposalNumber()); prepareHierarchySync(hierarchy); boolean isNewChild = false; boolean syncPersonnelAttachments = true; synchronizeChildProposal(hierarchy, childProposal, syncPersonnelAttachments, isNewChild); finalizeHierarchySync(hierarchy); } @Override public DevelopmentProposal lookupParent(DevelopmentProposal childProposal) throws ProposalHierarchyException { return getHierarchy(childProposal.getHierarchyParentProposalNumber()); } protected void linkChild(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, String hierarchyBudgetTypeCode, boolean syncPersonnelAttachments) throws ProposalHierarchyException { // set child to child status childProposal.setHierarchyStatus(HierarchyStatusConstants.Child.code()); childProposal.setHierarchyParentProposalNumber(hierarchyProposal.getProposalNumber()); childProposal.setHierarchyBudgetType(hierarchyBudgetTypeCode); // call synchronize boolean isNewChild = true; synchronizeChildProposal(hierarchyProposal, childProposal, syncPersonnelAttachments, isNewChild); } protected void copyInitialData(DevelopmentProposal hierarchyProposal, DevelopmentProposal srcProposal) throws ProposalHierarchyException { // Required fields for saving document hierarchyProposal.setHierarchyOriginatingChildProposalNumber(srcProposal.getProposalNumber()); hierarchyProposal.setSponsor(srcProposal.getSponsor()); hierarchyProposal.setSponsorCode(srcProposal.getSponsorCode()); hierarchyProposal.setProposalTypeCode(srcProposal.getProposalTypeCode()); hierarchyProposal.setRequestedStartDateInitial(srcProposal.getRequestedStartDateInitial()); hierarchyProposal.setRequestedEndDateInitial(srcProposal.getRequestedEndDateInitial()); hierarchyProposal.setOwnedByUnit(srcProposal.getOwnedByUnit()); hierarchyProposal.setOwnedByUnitNumber(srcProposal.getOwnedByUnitNumber()); hierarchyProposal.setActivityType(srcProposal.getActivityType()); hierarchyProposal.setActivityTypeCode(srcProposal.getActivityTypeCode()); hierarchyProposal.setTitle(srcProposal.getTitle()); // Sponsor & program information hierarchyProposal.setDeadlineDate(srcProposal.getDeadlineDate()); hierarchyProposal.setDeadlineTime(srcProposal.getDeadlineTime()); hierarchyProposal.setDeadlineType(srcProposal.getDeadlineType()); hierarchyProposal.setAnticipatedAwardTypeCode(srcProposal.getAnticipatedAwardTypeCode()); hierarchyProposal.setNoticeOfOpportunityCode(srcProposal.getNoticeOfOpportunityCode()); hierarchyProposal.setCfdaNumber(srcProposal.getCfdaNumber()); hierarchyProposal.setPrimeSponsorCode(srcProposal.getPrimeSponsorCode()); hierarchyProposal.setNsfCode(srcProposal.getNsfCode()); hierarchyProposal.setSponsorProposalNumber(srcProposal.getSponsorProposalNumber()); hierarchyProposal.setAgencyDivisionCode(srcProposal.getAgencyDivisionCode()); hierarchyProposal.setAgencyProgramCode(srcProposal.getAgencyProgramCode()); hierarchyProposal.setSubcontracts(srcProposal.getSubcontracts()); hierarchyProposal.setProgramAnnouncementNumber(srcProposal.getProgramAnnouncementNumber()); hierarchyProposal.setProgramAnnouncementTitle(srcProposal.getProgramAnnouncementTitle()); // Organization/location ProposalSite newSite; hierarchyProposal.getProposalSites().clear(); for (ProposalSite site : srcProposal.getProposalSites()) { newSite = (ProposalSite) deepCopy(site); newSite.setDevelopmentProposal(null); for (CongressionalDistrict cd : newSite.getCongressionalDistricts()) { cd.setProposalSite(newSite); } hierarchyProposal.addProposalSite(newSite); } // Delivery info hierarchyProposal.setMailBy(srcProposal.getMailBy()); hierarchyProposal.setMailType(srcProposal.getMailType()); hierarchyProposal.setMailAccountNumber(srcProposal.getMailAccountNumber()); hierarchyProposal.setNumberOfCopies(srcProposal.getNumberOfCopies()); hierarchyProposal.setMailingAddressId(srcProposal.getMailingAddressId()); hierarchyProposal.setMailDescription(srcProposal.getMailDescription()); } protected void copyOpportunity(DevelopmentProposal srcProposal, DevelopmentProposal hierarchyProposal) { if (srcProposal.getS2sOpportunity() != null) { S2sOpportunity opportunity = (S2sOpportunity) deepCopy(srcProposal.getS2sOpportunity()); opportunity.setDevelopmentProposal(hierarchyProposal); hierarchyProposal.setS2sOpportunity(opportunity); for (S2sOppForms form : opportunity.getS2sOppForms()) { form.getS2sOppFormsId().setProposalNumber(hierarchyProposal.getProposalNumber()); } } } protected KcDataObject deepCopy(KcDataObject oldObject) { return getDataObjectService().copyInstance(oldObject, CopyOption.RESET_OBJECT_ID, CopyOption.RESET_PK_FIELDS, CopyOption.RESET_VERSION_NUMBER); } /** * Synchronizes all child proposals to the parent. * @param hierarchyProposal * @return * @throws ProposalHierarchyException */ protected boolean synchronizeAllChildProposals(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException { boolean changed = false; // delete all multiple inst attachments right at the beginning deleteAllMultipleInternal(hierarchyProposal); finalizeHierarchySync(hierarchyProposal.getProposalDocument()); for (DevelopmentProposal childProposal : getHierarchyChildren(hierarchyProposal.getProposalNumber())) { List<BudgetPeriod> oldBudgetPeriods = getOldBudgetPeriods( proposalBudgetHierarchyService.getHierarchyBudget(hierarchyProposal)); ProposalPerson principalInvestigator = hierarchyProposal.getPrincipalInvestigator(); childProposal.setHierarchyLastSyncHashCode(computeHierarchyHashCode(childProposal)); removeChildElements(hierarchyProposal, childProposal.getProposalNumber()); synchronizeKeywords(hierarchyProposal, childProposal); synchronizeSpecialReviews(hierarchyProposal, childProposal); synchronizePersons(hierarchyProposal, childProposal, principalInvestigator); synchronizeNarratives(hierarchyProposal, childProposal); // we deleted all internal at the beginning so just add now. addInternalAttachments(hierarchyProposal, childProposal); syncDegreeInfo(hierarchyProposal, childProposal); boolean isNewChild = false; syncAllPersonnelAttachments(hierarchyProposal, childProposal, isNewChild); proposalBudgetHierarchyService.synchronizeChildBudget(hierarchyProposal, childProposal, oldBudgetPeriods); dataObjectService.save(childProposal); changed = true; } return changed; } protected void deleteAllMultipleInternal(DevelopmentProposal proposal) { List<Narrative> freshList = deleteAllMultipleTypeAttachments(proposal.getInstituteAttachments(), proposal); proposal.setInstituteAttachments(freshList); } protected List<Narrative> deleteAllMultipleTypeAttachments(List<Narrative> attachments, DevelopmentProposal proposal) { List<Narrative> freshList = new ArrayList<Narrative>(); for (Narrative narrative : attachments) { if (narrative.getNarrativeType().isAllowMultiple()) { narrative.setDevelopmentProposal(null); } else { freshList.add(narrative); } } return freshList; } /** * Synchronizes the given child proposal to the parent. * <p> * If a key proposal person appears in multiple child proposals and is removed from the given child * proposal, then this also aggregates that key person back to the parent proposal from a different child proposal, making sure that all the key persons * in all of the child proposals are represented in the parent proposal. * * @param hierarchyProposal * @param childProposal * @return * @throws ProposalHierarchyException */ protected boolean synchronizeChildProposal(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, boolean syncPersonnelAttachments, boolean isNewChild) throws ProposalHierarchyException { List<BudgetPeriod> oldBudgetPeriods = getOldBudgetPeriods( proposalBudgetHierarchyService.getHierarchyBudget(hierarchyProposal)); ProposalPerson principalInvestigator = hierarchyProposal.getPrincipalInvestigator(); childProposal.setHierarchyLastSyncHashCode(computeHierarchyHashCode(childProposal)); removeChildElements(hierarchyProposal, childProposal.getProposalNumber()); synchronizeKeywords(hierarchyProposal, childProposal); synchronizeSpecialReviews(hierarchyProposal, childProposal); synchronizePersonsAndAggregate(hierarchyProposal, childProposal, principalInvestigator); syncDegreeInfo(hierarchyProposal, childProposal); proposalBudgetHierarchyService.synchronizeChildBudget(hierarchyProposal, childProposal, oldBudgetPeriods); if (syncPersonnelAttachments) { synchronizeNarratives(hierarchyProposal, childProposal); synchronizeInternalAttachments(hierarchyProposal, childProposal); syncAllPersonnelAttachments(hierarchyProposal, childProposal, isNewChild); } dataObjectService.save(childProposal); return true; } public void reinstateDegreeInfo(DevelopmentProposal proposal) { for (ProposalPerson person : proposal.getProposalPersons()) { List<ProposalPersonDegree> degrees = getProposalHierarchyDao() .getDegreeInformation(proposal.getProposalNumber(), person); person.setProposalPersonDegrees(degrees); } } /** * Gets the old budget periods before removing them from the parent. * @param oldBudget * @return */ protected List<BudgetPeriod> getOldBudgetPeriods(Budget oldBudget) { List<BudgetPeriod> oldBudgetPeriods = new ArrayList<BudgetPeriod>(); oldBudgetPeriods.addAll(oldBudget.getBudgetPeriods()); return oldBudgetPeriods; } /** * Synchronizes the proposal science keywords from the child proposal to the parent proposal. * @param hierarchyProposal * @param childProposal */ protected void synchronizeKeywords(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { for (PropScienceKeyword keyword : childProposal.getPropScienceKeywords()) { PropScienceKeyword newKeyword = new PropScienceKeyword(hierarchyProposal, getScienceKeyword(keyword.getScienceKeyword().getCode())); if (!doesOldKeyWordExist(hierarchyProposal.getPropScienceKeywords(), newKeyword)) { newKeyword.setHierarchyProposalNumber(childProposal.getProposalNumber()); hierarchyProposal.addPropScienceKeyword(newKeyword); } } } protected boolean doesOldKeyWordExist(List<PropScienceKeyword> oldKeywords, PropScienceKeyword newKeyword) { for (PropScienceKeyword oldKeyWord : oldKeywords) { if (oldKeyWord.getScienceKeyword().getCode().equals(newKeyword.getScienceKeyword().getCode()) && oldKeyWord.getProposalNumber().equals(newKeyword.getProposalNumber())) { return true; } } return false; } protected ScienceKeyword getScienceKeyword(String code) { return getDataObjectService().findUnique(ScienceKeyword.class, QueryByCriteria.Builder.forAttribute("code", code).build()); } /** * Synchronizes the proposal special reviews from the child proposal to the parent proposal. * @param hierarchyProposal * @param childProposal */ protected void synchronizeSpecialReviews(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { for (ProposalSpecialReview review : childProposal.getPropSpecialReviews()) { ProposalSpecialReview newReview = (ProposalSpecialReview) deepCopy(review); newReview.setDevelopmentProposal(hierarchyProposal); newReview.setHierarchyProposalNumber(childProposal.getProposalNumber()); hierarchyProposal.getPropSpecialReviews().add(newReview); } } /** * Synchronizes the proposal narratives from the child proposal to the parent proposal based on some rules. * 1. If the attachment is single type, then will get created in the parent if one does not already exist there and then subsequent changes must be * made on the parent and will not allow sync to child. * 2. Multiple Attachment Type: If you have many attachments for the attachment type multiple, * then they all sync up and use the hierarchy proposal number to figure out how to sync. * These are never changed at the parent, though you could add one at the parent ( this will not have a hierarchy proposal number) that never * gets synced * 3. Any attachment can be added at the parent and then requires all updates on the parent. Child proposals not populated with this attachment. * @param hierarchyProposal * @param childProposal */ protected void synchronizeNarratives(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { // delete everything from parent that has this child proposal number as // as hierarchy proposal number and multiple attachment type. deleteMultipleTypeAttachments(hierarchyProposal, childProposal, childProposal.getNarratives()); syncAttachmentsFromChild(hierarchyProposal, childProposal, childProposal.getNarratives(), hierarchyProposal.getNarratives()); } protected void synchronizeInternalAttachments(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { // delete everything from parent that has this child proposal number as // as hierarchy proposal number and multiple attachment type. deleteMultipleTypeAttachments(hierarchyProposal, childProposal, childProposal.getInstituteAttachments()); syncAttachmentsFromChild(hierarchyProposal, childProposal, childProposal.getInstituteAttachments(), hierarchyProposal.getInstituteAttachments()); } protected void addInternalAttachments(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { syncAttachmentsFromChild(hierarchyProposal, childProposal, childProposal.getInstituteAttachments(), hierarchyProposal.getInstituteAttachments()); } protected void addNarratives(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { syncAttachmentsFromChild(hierarchyProposal, childProposal, childProposal.getInstituteAttachments(), hierarchyProposal.getNarratives()); } protected void syncAttachmentsFromChild(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, List<Narrative> childAttachments, List<Narrative> hierarchyAttachments) { for (Narrative narrative : childAttachments) { narrative.refreshReferenceObject("narrativeType"); // if single type narrative if (!narrative.getNarrativeType().isAllowMultiple() && !doesParentHaveNarrativeType(hierarchyProposal, narrative.getNarrativeType())) { addNarrativeToParent(hierarchyProposal, childProposal, narrative, hierarchyAttachments); // not adding user rights here since it is not needed. } // if attachment type allows multiple if (narrative.getNarrativeType().isAllowMultiple()) { addNarrativeToParent(hierarchyProposal, childProposal, narrative, hierarchyAttachments); } } } protected void addNarrativeToParent(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, Narrative narrative, List<Narrative> hierarchyAttachments) { Narrative newNarrative = (Narrative) deepCopy(narrative); newNarrative.setNarrativeTypeCode(narrative.getNarrativeTypeCode()); newNarrative.setNarrativeType(narrative.getNarrativeType()); newNarrative.refreshReferenceObject("narrativeType"); newNarrative.setHierarchyProposalNumber(childProposal.getProposalNumber()); newNarrative.setModuleStatusCode(Constants.NARRATIVE_MODULE_STATUS_INCOMPLETE); newNarrative.setModuleNumber( legacyNarrativeService.getNextModuleNumber(hierarchyProposal.getProposalDocument())); newNarrative.setDevelopmentProposal(hierarchyProposal); newNarrative.setNarrativeUserRights(null); newNarrative.getNarrativeAttachment().setData(narrative.getData()); hierarchyAttachments.add(newNarrative); } protected void deleteMultipleTypeAttachments(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, List<Narrative> attachments) { for (Narrative narrative : attachments) { narrative.refreshReferenceObject("narrativeType"); if (narrative.getNarrativeType().isAllowMultiple()) { deleteNarrativesFromParent(hierarchyProposal, childProposal, narrative.getNarrativeType()); } } } protected void deleteNarrativesFromParent(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, NarrativeType type) { Map<String, String> param = new HashMap<String, String>(); param.put("hierarchyProposalNumber", childProposal.getProposalNumber()); param.put("narrativeTypeCode", type.getCode()); getDataObjectService().deleteMatching(Narrative.class, QueryByCriteria.Builder.andAttributes(param).build()); } protected boolean doesParentHaveNarrativeType(DevelopmentProposal hierarchyProposal, NarrativeType narrativeType) { return getLegacyNarrativeService().doesProposalHaveNarrativeType(hierarchyProposal, narrativeType); } protected void syncAllPersonnelAttachments(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, boolean isNewChild) { Map<String, Boolean> personInMultipleProp = new HashMap<String, Boolean>(); List<ProposalPersonBiography> newList = new ArrayList<ProposalPersonBiography>(); /* Go thro list of personBios in child, if it is not in multiple proposals, then remove instance from parent so you can copy over again. */ for (Iterator<ProposalPersonBiography> iteratorChild = childProposal.getPropPersonBios() .iterator(); iteratorChild.hasNext();) { ProposalPersonBiography srcPropPersonBio = iteratorChild.next(); // if the proposal is JUST being linked to a hierarchy and if this proposal has bios for // people that exist on the parent, ignore those bios. if (!isBioInNewChildDuplicate(isNewChild, hierarchyProposal, srcPropPersonBio) && !personInMultipleProposals(srcPropPersonBio.getPersonId(), hierarchyProposal)) { // mark those persons that are not in multiple proposals // and remove this persons bio from parent // since they will be copied over again later. We need to do this so if a bio is updated at the // child it will sync up. personInMultipleProp.put(srcPropPersonBio.getPersonId(), false); for (Iterator<ProposalPersonBiography> iteratorParent = hierarchyProposal.getPropPersonBios() .iterator(); iteratorParent.hasNext();) { ProposalPersonBiography parentPropPersonBio = iteratorParent.next(); if (StringUtils.equals(srcPropPersonBio.getPersonId(), parentPropPersonBio.getPersonId())) { iteratorParent.remove(); } } } } // go over the child bios list and if person is not in multiple proposals add it for (ProposalPersonBiography srcPropPersonBio : childProposal.getPropPersonBios()) { // if the proposal is JUST being linked to a hierarchy and if this proposal has bios for // people that exist on the parent, ignore those bios. if (!isBioInNewChildDuplicate(isNewChild, hierarchyProposal, srcPropPersonBio) && personInMultipleProp.get(srcPropPersonBio.getPersonId()) != null && !personInMultipleProp.get(srcPropPersonBio.getPersonId())) { ProposalPersonBiography destPropPersonBio; destPropPersonBio = (ProposalPersonBiography) deepCopy(srcPropPersonBio); destPropPersonBio.setDevelopmentProposal(hierarchyProposal); destPropPersonBio.setProposalNumber(hierarchyProposal.getProposalNumber()); destPropPersonBio.setProposalPersonNumber( getProposalPersonNumber(destPropPersonBio.getPersonId(), hierarchyProposal)); destPropPersonBio.setVersionNumber(0L); newList.add(destPropPersonBio); } } hierarchyProposal.getPropPersonBios().addAll(newList); } protected boolean isBioInNewChildDuplicate(boolean isNewChild, DevelopmentProposal hierarchyProposal, ProposalPersonBiography srcPropPersonBio) { return isNewChild && isPersonOnParent(hierarchyProposal, srcPropPersonBio.getPersonId(), srcPropPersonBio); } protected Integer getProposalPersonNumber(String personId, DevelopmentProposal hierarchyProposal) { for (ProposalPerson person : hierarchyProposal.getProposalPersons()) { if (StringUtils.equalsIgnoreCase(person.getPersonId(), personId)) { return person.getProposalPersonNumber(); } } return null; } public boolean personInMultipleProposals(String personId, DevelopmentProposal proposal) { if (proposal.isChild()) { return getProposalHierarchyDao().personInMultipleChildProposals(personId, proposal.getHierarchyParentProposalNumber()); } else { return getProposalHierarchyDao().personInMultipleChildProposals(personId, proposal.getProposalNumber()); } } protected boolean isPersonOnParent(DevelopmentProposal proposal, String id, ProposalPersonBiography srcPropPersonBio) { List<ProposalPerson> persons = getProposalHierarchyDao().isPersonOnProposal(proposal.getProposalNumber(), id); // if person is on parent, then check if the person has been added by the same proposal linked to // the current bio. If latter is true, the srcBio can be added, if not, bio has been added by a different proposal // and needs to be maintained at the parent. return persons.size() > 0 && persons.get(0).getHierarchyProposalNumber() != srcPropPersonBio .getDevelopmentProposal().getProposalNumber(); } /** * Synchronizes the proposal persons from the child proposal to the parent proposal and then restores any proposal persons that were in the given child * proposal (and hence removed from the given parent proposal). * <p> * This first synchronizes the main proposal persons from the primary child proposal to the parent proposal and then runs the same algorithm on all other * children of the parent proposal. * @param hierarchyProposal * @param primaryChildProposal * @param principalInvestigator */ protected void synchronizePersonsAndAggregate(DevelopmentProposal hierarchyProposal, DevelopmentProposal primaryChildProposal, ProposalPerson principalInvestigator) { synchronizePersons(hierarchyProposal, primaryChildProposal, principalInvestigator); for (DevelopmentProposal childProposal : getHierarchyChildren(hierarchyProposal.getProposalNumber())) { if (!StringUtils.equals(primaryChildProposal.getProposalNumber(), childProposal.getProposalNumber())) { synchronizePersons(hierarchyProposal, childProposal, principalInvestigator); } } } /** * Synchronizes the proposal persons from the child proposal to the parent proposal. * @param hierarchyProposal * @param childProposal * @param principalInvestigator */ protected void synchronizePersons(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, ProposalPerson principalInvestigator) { for (ProposalPerson person : childProposal.getProposalPersons()) { int firstIndex = hierarchyProposal.getProposalPersons().indexOf(person); int lastIndex = hierarchyProposal.getProposalPersons().lastIndexOf(person); ProposalPerson firstInstance = (firstIndex == -1) ? null : hierarchyProposal.getProposalPersons().get(firstIndex); if (firstIndex == -1 || (firstIndex == lastIndex && !rolesAreSimilar(person, firstInstance))) { ProposalPerson newPerson; newPerson = (ProposalPerson) deepCopy(person); newPerson.setDevelopmentProposal(hierarchyProposal); newPerson.getProposalPersonYnqs().clear(); for (ProposalPersonUnit unit : newPerson.getUnits()) { for (ProposalUnitCreditSplit creditSplit : unit.getCreditSplits()) { creditSplit.setCredit(new ScaleTwoDecimal(0)); } } for (ProposalPersonCreditSplit creditSplit : newPerson.getCreditSplits()) { creditSplit.setCredit(new ScaleTwoDecimal(0)); } newPerson.setProposalPersonNumber(null); newPerson.setVersionNumber(null); newPerson.setHierarchyProposalNumber(childProposal.getProposalNumber()); if (StringUtils.equalsIgnoreCase(person.getProposalPersonRoleId(), Constants.PRINCIPAL_INVESTIGATOR_ROLE)) { newPerson.setProposalPersonRoleId(Constants.CO_INVESTIGATOR_ROLE); } if (newPerson.equals(principalInvestigator) && (firstIndex == -1 || !firstInstance.isInvestigator())) { newPerson.setProposalPersonRoleId(Constants.PRINCIPAL_INVESTIGATOR_ROLE); } hierarchyProposal.addProposalPerson(newPerson); } } removeDeletedPersonnelFromParent(hierarchyProposal, childProposal); } protected void syncDegreeInfo(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { for (ProposalPerson person : childProposal.getProposalPersons()) { int firstIndex = hierarchyProposal.getProposalPersons().indexOf(person); syncDegreeInfo(hierarchyProposal, firstIndex, person); } } protected void syncDegreeInfo(DevelopmentProposal hierarchyProposal, int indexOfPersonInParent, ProposalPerson childPerson) { ProposalPerson personInParent = hierarchyProposal.getProposalPersons().get(indexOfPersonInParent); // if this person is not null and this person is not new then add degrees. if (personInParent != null && personInParent.getVersionNumber() != null) { if (!personInMultipleProposals(personInParent.getPersonId(), hierarchyProposal)) { getProposalHierarchyDao().deleteDegreeInfo(hierarchyProposal.getProposalNumber(), personInParent.getProposalPersonNumber(), personInParent); for (ProposalPersonDegree degree : childPerson.getProposalPersonDegrees()) { ProposalPersonDegree newDegree = new ProposalPersonDegree(); newDegree.setDegree(degree.getDegree()); newDegree.setDegreeCode(degree.getDegreeCode()); newDegree.setProposalPerson(personInParent); newDegree.setDegreeType(degree.getDegreeType()); newDegree.setFieldOfStudy(degree.getFieldOfStudy()); newDegree.setGraduationYear(degree.getGraduationYear()); newDegree.setSchool(degree.getSchool()); newDegree.setSpecialization(degree.getSpecialization()); newDegree.setDegreeSequenceNumber(getDocument(hierarchyProposal) .getDocumentNextValue(Constants.PROPOSAL_PERSON_DEGREE_SEQUENCE_NUMBER)); newDegree = dataObjectService.save(newDegree); } } } } protected ProposalDevelopmentDocument getDocument(DevelopmentProposal proposal) { ProposalDevelopmentDocument doc; try { doc = (ProposalDevelopmentDocument) documentService .getByDocumentHeaderId(proposal.getProposalDocument().getDocumentNumber()); } catch (WorkflowException e) { throw new RuntimeException("Cannot find the proposal", e); } return doc; } public DevelopmentProposal getHierarchy(String hierarchyProposalNumber) throws ProposalHierarchyException { DevelopmentProposal hierarchy = getDevelopmentProposal(hierarchyProposalNumber); if (hierarchy == null || !hierarchy.isParent()) throw new ProposalHierarchyException("Proposal " + hierarchyProposalNumber + " is not a hierarchy"); return hierarchy; } public boolean isSynchronized(DevelopmentProposal childProposal) { Integer hc1 = computeHierarchyHashCode(childProposal); Integer hc2 = childProposal.getHierarchyLastSyncHashCode(); return org.apache.commons.lang3.ObjectUtils.equals(hc1, hc2); } protected boolean isBudgetSynchronized(DevelopmentProposal childProposal, ProposalDevelopmentBudgetExt childBudget) throws ProposalHierarchyException { if (Objects.equals(childProposal.getLastSyncedBudget(), childBudget)) { ObjectUtils.materializeAllSubObjects(childBudget); Integer hc1 = proposalBudgetHierarchyService.computeHierarchyHashCode(childBudget); Integer hc2 = childBudget.getHierarchyLastSyncHashCode(); return org.apache.commons.lang3.ObjectUtils.equals(hc1, hc2); } else { return false; } } protected void setInitialPi(DevelopmentProposal hierarchy, DevelopmentProposal child) { ProposalPerson pi = child.getPrincipalInvestigator(); if (pi != null) { int index = hierarchy.getProposalPersons().indexOf(pi); if (index > -1) { hierarchy.getProposalPerson(index).setProposalPersonRoleId(Constants.PRINCIPAL_INVESTIGATOR_ROLE); } } } public List<ProposalHierarchyErrorWarningDto> validateChildBudgetPeriods(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal, boolean allowEndDateChange) throws ProposalHierarchyException { return proposalBudgetHierarchyService.validateChildBudgetPeriods(hierarchyProposal, childProposal, allowEndDateChange); } public void synchronizeChildBudget(DevelopmentProposal hierarchyProposal, ProposalDevelopmentBudgetExt budget) { prepareHierarchySync(hierarchyProposal); proposalBudgetHierarchyService.synchronizeChildBudget(hierarchyProposal, budget); finalizeHierarchySync(hierarchyProposal); } public ProposalDevelopmentBudgetExt getSyncableBudget(DevelopmentProposal proposal) { return proposalBudgetHierarchyService.getSyncableBudget(proposal); } public boolean needToExtendProjectDate(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) { if (hierarchyProposal != null && !hierarchyProposal.getBudgets().isEmpty()) { ProposalDevelopmentBudgetExt parentBudget = proposalBudgetHierarchyService .getHierarchyBudget(hierarchyProposal); Budget childBudget = getSyncableBudget(childProposal); BudgetPeriod lastParentPeriod = parentBudget.getBudgetPeriods() .get(parentBudget.getBudgetPeriods().size() - 1); BudgetPeriod lastChildPeriod = childBudget.getBudgetPeriods() .get(childBudget.getBudgetPeriods().size() - 1); return lastChildPeriod.getStartDate().after(lastParentPeriod.getEndDate()); } else { return false; } } public boolean needToExtendProjectDate(DevelopmentProposal hierarchyProposal) { List<DevelopmentProposal> proposals = this.getHierarchyProposals(hierarchyProposal); for (DevelopmentProposal proposal : proposals) { if (needToExtendProjectDate(hierarchyProposal, proposal)) { return true; } } return false; } protected void removeChildElements(DevelopmentProposal parentProposal, String childProposalNumber) { List<ProposalSpecialReview> reviews = parentProposal.getPropSpecialReviews(); for (int i = reviews.size() - 1; i >= 0; i--) { if (StringUtils.equals(childProposalNumber, reviews.get(i).getHierarchyProposalNumber())) { reviews.remove(i); } } List<Narrative> narratives = parentProposal.getNarratives(); for (int i = narratives.size() - 1; i >= 0; i--) { if (StringUtils.equals(childProposalNumber, narratives.get(i).getHierarchyProposalNumber())) { dataObjectService.delete(narratives.remove(i)); } } ProposalDevelopmentBudgetExt parentBudget = proposalBudgetHierarchyService .getHierarchyBudget(parentProposal); if (parentBudget != null) { proposalBudgetHierarchyService.removeChildBudgetElements(parentProposal, parentBudget, childProposalNumber); } } protected void prepareHierarchySync(DevelopmentProposal hierarchyProposal) { prepareHierarchySync(hierarchyProposal.getProposalDocument()); } protected void prepareHierarchySync(ProposalDevelopmentDocument pdDoc) { pdDoc.refreshReferenceObject("documentNextvalues"); } protected void finalizeHierarchySync(ProposalDevelopmentDocument pdDoc) throws ProposalHierarchyException { try { documentService.saveDocument(pdDoc); } catch (WorkflowException e) { throw new ProposalHierarchyException(e); } } protected DevelopmentProposal finalizeHierarchySync(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException { DevelopmentProposal savedParentProposal = dataObjectService.save(hierarchyProposal); dataObjectService.save(proposalBudgetHierarchyService.getHierarchyBudget(hierarchyProposal)); return savedParentProposal; } /* This is the first copy from lead child to parent, so copy EVERY NARRATIVE over. Narrative includes institute attachments. */ protected void copyInitialAttachments(DevelopmentProposal srcProposal, DevelopmentProposal destProposal) { ProposalPersonBiography destPropPersonBio; ProposalPerson srcPerson = null; ProposalPerson destPerson = null; for (ProposalPersonBiography srcPropPersonBio : srcProposal.getPropPersonBios()) { for (ProposalPerson person : srcProposal.getProposalPersons()) { if (person.getProposalPersonNumber().equals(srcPropPersonBio.getProposalPersonNumber())) { srcPerson = person; break; } } for (ProposalPerson person : destProposal.getProposalPersons()) { if (person.equals(srcPerson)) { destPerson = person; break; } } destPropPersonBio = (ProposalPersonBiography) deepCopy(srcPropPersonBio); destPropPersonBio.setDevelopmentProposal(destProposal); destPropPersonBio.setProposalNumber(destProposal.getProposalNumber()); destPropPersonBio.setProposalPersonNumber(destPerson.getProposalPersonNumber()); destPropPersonBio.setPersonId(destPerson.getPersonId()); destPropPersonBio.setRolodexId(destPerson.getRolodexId()); destPropPersonBio.getPersonnelAttachment().setData(srcPropPersonBio.getData()); destProposal.getPropPersonBios().add(destPropPersonBio); } for (Narrative srcNarrative : srcProposal.getNarratives()) { addNarrativeToParent(destProposal, srcProposal, srcNarrative, destProposal.getNarratives()); } for (Narrative narrative : destProposal.getNarratives()) { narrative.setNarrativeUserRights(null); } for (Narrative attInternal : srcProposal.getInstituteAttachments()) { addNarrativeToParent(destProposal, srcProposal, attInternal, destProposal.getInstituteAttachments()); } } /** * Creates a hash of the data pertinent to a hierarchy for comparison during hierarchy syncing. */ protected int computeHierarchyHashCode(DevelopmentProposal proposal) { int prime = 31; int result = 1; for (ProposalPerson person : proposal.getProposalPersons()) { result = prime * result + person.hashCode(); } for (Narrative narrative : proposal.getNarratives()) { result = prime * result + narrative.hierarchyHashCode(); } for (PropScienceKeyword keyword : proposal.getPropScienceKeywords()) { result = prime * result + keyword.getScienceKeyword().getCode().hashCode(); } for (ProposalSpecialReview review : proposal.getPropSpecialReviews()) { result = prime * result + review.hierarchyHashCode(); } return result; } @Override public List<DevelopmentProposal> getHierarchyChildren(String parentProposalNumber) { List<DevelopmentProposal> children = new ArrayList<DevelopmentProposal>(); for (String childProposalNumber : proposalHierarchyDao .getHierarchyChildProposalNumbers(parentProposalNumber)) { children.add(getDevelopmentProposal(childProposalNumber)); } return children; } @Override public WorkflowDocument getParentWorkflowDocument(ProposalDevelopmentDocument child) throws ProposalHierarchyException { return getParentDocument(child).getDocumentHeader().getWorkflowDocument(); } @Override public ProposalDevelopmentDocument getParentDocument(ProposalDevelopmentDocument child) throws ProposalHierarchyException { try { DevelopmentProposal parentProposal = getHierarchy( child.getDevelopmentProposal().getHierarchyParentProposalNumber()); String parentDocumentNumber = parentProposal.getProposalDocument().getDocumentNumber(); return (ProposalDevelopmentDocument) documentService.getByDocumentHeaderId(parentDocumentNumber); } catch (WorkflowException e) { throw new ProposalHierarchyException( String.format("Could not lookup hierarchy workflow status for child:%s", child.getDocumentHeader().getDocumentNumber()), e); } } /** * Reject a proposal by sending it to the first node ( as named by PROPOSALDEVELOPMENTDOCUMENT_KEW_INITIAL_NODE_NAME ) * @param proposalDoc The ProposalDevelopmentDocument that should be rejected. * @param appDocStatus the application status to set in the workflow document. * @param principalId the principal we are rejecting the document as. * @param appDocStatus the application document status to apply ( does not apply if null ) * @throws WorkflowException */ protected void rejectProposal(ProposalDevelopmentDocument proposalDoc, String reason, String principalId, String appDocStatus) throws WorkflowException { kcDocumentRejectionService.reject(proposalDoc.getDocumentHeader().getWorkflowDocument(), reason, principalId, appDocStatus); } /** * Reject an entire proposal hierarchy. * @param hierarchyParent The hierarchy to reject * @param reason the reason to be applied to the annotation field. The reason will be pre-pended with static text indicating if it was a child or the parent. * @param principalId the id of the principal that is rejecting the document. * @throws ProposalHierarchyException If hierarchyParent is not a hierarchy, or there was a problem rejecting one of the documents. */ protected void rejectProposalHierarchy(ProposalDevelopmentDocument hierarchyParent, String reason, String principalId) throws ProposalHierarchyException { //1. reject the parent. try { rejectProposal(hierarchyParent, renderMessage(PROPOSAL_ROUTING_REJECTED_ANNOTATION, reason), principalId, renderMessage(HIERARCHY_REJECTED_APPSTATUS)); } catch (WorkflowException e) { throw new ProposalHierarchyException( String.format("WorkflowException encountered rejecting proposal hierarchy parent %s", hierarchyParent.getDevelopmentProposal().getProposalNumber()), e); } } @Override public void rejectProposalDevelopmentDocument(String proposalNumber, String reason, String principalName, MultipartFile rejectFile) throws WorkflowException, ProposalHierarchyException, IOException { DevelopmentProposal pbo = getDevelopmentProposal(proposalNumber); ProposalDevelopmentDocument pDoc = (ProposalDevelopmentDocument) documentService .getByDocumentHeaderId(pbo.getProposalDocument().getDocumentNumber()); if (!pbo.isInHierarchy()) { rejectProposal(pDoc, renderMessage(PROPOSAL_ROUTING_REJECTED_ANNOTATION, reason), principalName, renderMessage(HIERARCHY_REJECTED_APPSTATUS)); } else if (pbo.isParent()) { rejectProposalHierarchy(pDoc, reason, principalName); } else { //it is a child or in some unknown state, either way we do not support rejecting it. throw new UnsupportedOperationException( String.format("Cannot reject proposal %s it is a hierarchy child or ", proposalNumber)); } if (rejectFile != null && rejectFile.getBytes().length > 0) { Narrative narrative = new Narrative(); narrative.setName(rejectFile.getOriginalFilename()); narrative.setComments(reason); try { narrative.init(rejectFile); } catch (Exception e) { throw new RuntimeException("Error Initializing narrative attachment file", e); } narrative.setNarrativeTypeCode(getParameterService().getParameterValueAsString( ProposalDevelopmentDocument.class, Constants.REJECT_NARRATIVE_TYPE_CODE_PARAM)); NarrativeStatus status = (NarrativeStatus) dataObjectService.findUnique(NarrativeStatus.class, QueryByCriteria.Builder.forAttribute("code", "C").build()); narrative.setNarrativeStatus(status); narrative.setModuleStatusCode(status.getCode()); narrative.setModuleTitle("Proposal rejection attachment."); narrative.setContactName(globalVariableService.getUserSession().getPrincipalName()); narrative.setPhoneNumber(globalVariableService.getUserSession().getPerson().getPhoneNumber()); narrative.setEmailAddress(globalVariableService.getUserSession().getPerson().getEmailAddress()); getLegacyNarrativeService().prepareNarrative(pDoc, narrative); pDoc.getDevelopmentProposal().getInstituteAttachments().add(narrative); dataObjectService.save(pDoc); } } public void calculateAndSetProposalAppDocStatus(ProposalDevelopmentDocument doc, DocumentRouteStatusChange dto) throws ProposalHierarchyException { String principalId = globalVariableService.getUserSession().getPrincipalId(); if (StringUtils.equals(dto.getNewRouteStatus(), KewApiConstants.ROUTE_HEADER_ENROUTE_CD)) { updateAppDocStatus(doc, principalId, HIERARCHY_ENROUTE_APPSTATUS); } else if (StringUtils.equals(dto.getNewRouteStatus(), KewApiConstants.ROUTE_HEADER_CANCEL_CD)) { updateAppDocStatus(doc, principalId, HIERARCHY_CANCEL_APPSTATUS); } else if (StringUtils.equals(dto.getNewRouteStatus(), KewApiConstants.ROUTE_HEADER_DISAPPROVED_CD)) { updateAppDocStatus(doc, principalId, HIERARCHY_DISAPPROVE_APPSTATUS); } else if (StringUtils.equals(dto.getNewRouteStatus(), KewApiConstants.ROUTE_HEADER_FINAL_CD)) { updateAppDocStatus(doc, principalId, HIERARCHY_FINAL_APPSTATUS); } else if (StringUtils.equals(dto.getNewRouteStatus(), KewApiConstants.ROUTE_HEADER_PROCESSED_CD)) { updateAppDocStatus(doc, principalId, HIERARCHY_PROCESSED_APPSTATUS); } } protected void updateAppDocStatus(ProposalDevelopmentDocument doc, String principalId, String newStatus) throws ProposalHierarchyException { try { WorkflowDocument wdoc = WorkflowDocumentFactory.loadDocument(principalId, doc.getDocumentHeader().getWorkflowDocument().getDocumentId()); wdoc.setApplicationDocumentStatus(renderMessage(newStatus)); } catch (Exception e) { throw new ProposalHierarchyException(String.format( "Exception encountrered while attempting to update App Doc Status of proposal %s ( document #%s )", doc.getDevelopmentProposal().getProposalNumber(), doc.getDocumentNumber()), e); } } public boolean allChildBudgetsAreComplete(String parentProposalNumber) { boolean retval = true; String completeCode = parameterService.getParameterValueAsString(Budget.class, Constants.BUDGET_STATUS_COMPLETE_CODE); for (ProposalBudgetStatus status : proposalHierarchyDao .getHierarchyChildProposalBudgetStatuses(parentProposalNumber)) { if (!StringUtils.equalsIgnoreCase(completeCode, status.getBudgetStatusCode())) { retval = false; break; } } return retval; } protected boolean rolesAreSimilar(ProposalPerson person1, ProposalPerson person2) { boolean isInvestigator1 = StringUtils.equals(person1.getProposalPersonRoleId(), Constants.PRINCIPAL_INVESTIGATOR_ROLE) || StringUtils.equals(person1.getProposalPersonRoleId(), Constants.CO_INVESTIGATOR_ROLE); boolean isInvestigator2 = StringUtils.equals(person2.getProposalPersonRoleId(), Constants.PRINCIPAL_INVESTIGATOR_ROLE) || StringUtils.equals(person2.getProposalPersonRoleId(), Constants.CO_INVESTIGATOR_ROLE); return isInvestigator1 == isInvestigator2; } @Override public List<HierarchyPersonnelSummary> getHierarchyPersonnelSummaries(String parentProposalNumber) throws ProposalHierarchyException { List<HierarchyPersonnelSummary> summaries = new ArrayList<HierarchyPersonnelSummary>(); List<String> proposalNumbers = new ArrayList<String>(); proposalNumbers.addAll(proposalHierarchyDao.getHierarchyChildProposalNumbers(parentProposalNumber)); Collections.sort(proposalNumbers); for (String proposalNumber : proposalNumbers) { HierarchyPersonnelSummary summary = new HierarchyPersonnelSummary(); DevelopmentProposal childProposal = getDevelopmentProposal(proposalNumber); List<Budget> hierarchyBudgets = new ArrayList<Budget>(); for (ProposalDevelopmentBudgetExt hierarchyBudget : proposalBudgetHierarchyService .getHierarchyBudgets(childProposal)) { hierarchyBudgets.add(hierarchyBudget); } Collections.sort(hierarchyBudgets); summary.setProposalNumber(proposalNumber); summary.setHierarchyBudgets(hierarchyBudgets); summaries.add(summary); } return summaries; } @Override public List<HierarchyProposalSummary> getHierarchyProposalSummaries(String proposalNumber) throws ProposalHierarchyException { DevelopmentProposal proposal = getDevelopmentProposal(proposalNumber); List<HierarchyProposalSummary> summaries = new ArrayList<HierarchyProposalSummary>(); List<String> proposalNumbers = new ArrayList<String>(); if (proposal.isParent()) { proposalNumbers.add(proposalNumber); } else if (proposal.isChild()) { proposalNumbers.add(proposal.getHierarchyParentProposalNumber()); } else { throw new ProposalHierarchyException("Proposal " + proposalNumber + " is not a member of a hierarchy."); } proposalNumbers.addAll(proposalHierarchyDao.getHierarchyChildProposalNumbers(proposalNumbers.get(0))); HierarchyProposalSummary summary; for (String number : proposalNumbers) { summary = new HierarchyProposalSummary(); summary.setProposalNumber(number); if (!StringUtils.equals(number, proposalNumbers.get(0))) { summary = getProposalSummary(number); } summaries.add(summary); } return summaries; } public HierarchyProposalSummary getProposalSummary(String proposalNumber) throws ProposalHierarchyException { HierarchyProposalSummary summary = new HierarchyProposalSummary(); summary.setProposalNumber(proposalNumber); DevelopmentProposal childProposal = getDevelopmentProposal(proposalNumber); summary.setSynced(isSynchronized(childProposal)); ProposalDevelopmentBudgetExt budget = getSyncableBudget(childProposal); summary.setBudgetSynced(isBudgetSynchronized(childProposal, budget)); return summary; } @Override public List<DevelopmentProposal> getHierarchyProposals(DevelopmentProposal proposal) { List<DevelopmentProposal> hierachyProposals = new ArrayList<DevelopmentProposal>(); List<String> proposalNumbers = new ArrayList<String>(); if (proposal.isParent()) { proposalNumbers.add(proposal.getProposalNumber()); } else if (proposal.isChild()) { proposalNumbers.add(proposal.getHierarchyParentProposalNumber()); } else { return hierachyProposals; } proposalNumbers.addAll(proposalHierarchyDao.getHierarchyChildProposalNumbers(proposalNumbers.get(0))); hierachyProposals.addAll(getDataObjectService() .findMatching(DevelopmentProposal.class, QueryByCriteria.Builder .andAttributes(Collections.singletonMap("proposalNumber", proposalNumbers)) .setOrderByFields(OrderByField.Builder .create("hierarchyStatus", OrderDirection.DESCENDING).build()) .build()) .getResults()); return hierachyProposals; } public boolean validateRemovePermissions(DevelopmentProposal childProposal, String principalId) { boolean valid = true; valid &= kcAuthorizationService.hasPermission(principalId, childProposal.getProposalDocument(), PermissionConstants.MAINTAIN_PROPOSAL_HIERARCHY); try { valid &= kcAuthorizationService.hasPermission(principalId, getHierarchy(childProposal.getHierarchyParentProposalNumber()).getProposalDocument(), PermissionConstants.MAINTAIN_PROPOSAL_HIERARCHY); } catch (ProposalHierarchyException e) { valid = false; } return valid; } public List<ProposalHierarchyErrorWarningDto> validateChildForRemoval(DevelopmentProposal child) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); try { DevelopmentProposal hierarchy = lookupParent(child); if (hasCompleteBudget(hierarchy)) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_REMOVE_PARENT_BUDGET_COMPLETE, Boolean.TRUE, new String[0])); } } catch (ProposalHierarchyException e) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_UNEXPECTED, Boolean.TRUE, e.getMessage())); } return errors; } protected String renderMessage(String key, String... params) { String msg = kualiConfigurationService.getPropertyValueAsString(key); for (int i = 0; i < params.length; i++) { msg = replace(msg, "{" + i + "}", params[i]); } return msg; } protected KcDocumentRejectionService getKcDocumentRejectionService() { return kcDocumentRejectionService; } protected DocumentService getDocumentService() { return documentService; } protected KcAuthorizationService getKcAuthorizationService() { return kcAuthorizationService; } protected ProposalHierarchyDao getProposalHierarchyDao() { return proposalHierarchyDao; } protected LegacyNarrativeService getLegacyNarrativeService() { return legacyNarrativeService; } protected ProposalPersonBiographyService getProposalPersonBiographyService() { return proposalPersonBiographyService; } public void setProposalPersonBiographyService(ProposalPersonBiographyService proposalPersonBiographyService) { this.proposalPersonBiographyService = proposalPersonBiographyService; } protected ParameterService getParameterService() { return parameterService; } protected IdentityService getIdentityManagementService() { return identityService; } protected ConfigurationService getKualiConfigurationService() { return kualiConfigurationService; } public IdentityService getIdentityService() { return identityService; } public GlobalVariableService getGlobalVariableService() { return globalVariableService; } public void setGlobalVariableService(GlobalVariableService globalVariableService) { this.globalVariableService = globalVariableService; } public List<ProposalHierarchyErrorWarningDto> validateChildForSync(DevelopmentProposal child, DevelopmentProposal hierarchy, boolean allowEndDateChange) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); boolean valid = true; if (child.getPrincipalInvestigator() == null) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_SYNC_NO_PRINCIPLE_INVESTIGATOR, Boolean.TRUE, child.getProposalNumber())); } errors.addAll(validateSponsor(child, hierarchy)); errors.addAll(validateIsParentLocked(hierarchy)); errors.addAll(validateParent(hierarchy)); try { // add budget validation here. } catch (ProposalHierarchyException e) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_UNEXPECTED, Boolean.TRUE, e.getMessage())); } return errors; } public List<ProposalHierarchyErrorWarningDto> validateChildCandidate(DevelopmentProposal proposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (proposal.isInHierarchy()) { errors.add( new ProposalHierarchyErrorWarningDto(ERROR_LINK_ALREADY_MEMBER, Boolean.TRUE, new String[0])); } if (proposal.getBudgets().isEmpty()) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_LINK_NO_BUDGET_VERSION, Boolean.TRUE, new String[0])); } else { if (!hasFinalBudget(proposal)) { errors.add(new ProposalHierarchyErrorWarningDto(WARNING_LINK_NO_FINAL_BUDGET, Boolean.FALSE, new String[] { proposal.getProposalNumber() })); } } if (proposal.getPrincipalInvestigator() == null) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_LINK_NO_PRINCIPLE_INVESTIGATOR, Boolean.TRUE, new String[0])); } return errors; } public List<ProposalHierarchyErrorWarningDto> validateChildCandidateForHierarchy(DevelopmentProposal hierarchy, DevelopmentProposal child, boolean allowEndDateChange) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); boolean valid = true; if (!StringUtils.equalsIgnoreCase(hierarchy.getSponsorCode(), child.getSponsorCode())) { errors.add(new ProposalHierarchyErrorWarningDto(WARNING_LINK_DIFFERENT_SPONSOR, Boolean.FALSE, new String[0])); } try { // add budget validation here } catch (ProposalHierarchyException e) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_UNEXPECTED, Boolean.TRUE, e.getMessage())); } return errors; } public List<ProposalHierarchyErrorWarningDto> validateParent(DevelopmentProposal proposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (!proposal.isParent()) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_LINK_NOT_PARENT, Boolean.TRUE, new String[0])); } if (hasCompleteBudget(proposal)) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_LINK_PARENT_BUDGET_COMPLETE, Boolean.TRUE, new String[0])); } return errors; } public List<ProposalHierarchyErrorWarningDto> validateSponsor(DevelopmentProposal childProposal, DevelopmentProposal parentProposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (!StringUtils.equals(childProposal.getSponsorCode(), parentProposal.getSponsorCode())) { errors.add( new ProposalHierarchyErrorWarningDto(ERROR_DIFFERENT_SPONSORS, Boolean.FALSE, new String[0])); } return errors; } protected List<ProposalHierarchyErrorWarningDto> validateIsAggregatorOnParent( DevelopmentProposal parentProposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (!getKcAuthorizationService().hasPermission(getGlobalVariableService().getUserSession().getPrincipalId(), parentProposal.getDocument(), PermissionConstants.MAINTAIN_PROPOSAL_HIERARCHY)) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_NOT_PARENT_AGGREGATOR, Boolean.TRUE, new String[] { parentProposal.getProposalNumber() })); } return errors; } public List<ProposalHierarchyErrorWarningDto> validateIsAggregatorOnChild(DevelopmentProposal childProposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (!getKcAuthorizationService().hasPermission(getGlobalVariableService().getUserSession().getPrincipalId(), childProposal.getDocument(), PermissionConstants.MAINTAIN_PROPOSAL_HIERARCHY)) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_NOT_CHILD_AGGREGATOR, Boolean.TRUE, new String[] { childProposal.getProposalNumber() })); } return errors; } protected List<ProposalHierarchyErrorWarningDto> validateIsParentLocked(DevelopmentProposal parentProposal) { List<ProposalHierarchyErrorWarningDto> errors = new ArrayList<ProposalHierarchyErrorWarningDto>(); if (!getPessimisticLockService() .getPessimisticLocksForDocument(parentProposal.getDocument().getDocumentNumber()).isEmpty()) { errors.add(new ProposalHierarchyErrorWarningDto(ERROR_PARENT_LOCK, Boolean.TRUE, new String[] { parentProposal.getProposalNumber() })); } return errors; } private boolean hasFinalBudget(DevelopmentProposal proposal) { return proposal.getFinalBudget() != null; } private boolean hasCompleteBudget(DevelopmentProposal developmentProposal) { boolean retval = false; String completeCode = getParameterService().getParameterValueAsString(Budget.class, Constants.BUDGET_STATUS_COMPLETE_CODE); for (ProposalDevelopmentBudgetExt version : developmentProposal.getBudgets()) { if (!(version.getBudgetStatus() == null) && version.getBudgetStatus().equalsIgnoreCase(completeCode)) { retval = true; break; } } return retval; } protected DataObjectService getDataObjectService() { return dataObjectService; } public void setDataObjectService(DataObjectService dataObjectService) { this.dataObjectService = dataObjectService; } public void setKeyPersonnelService(KeyPersonnelService keyPersonnelService) { this.keyPersonnelService = keyPersonnelService; } public KeyPersonnelService getKeyPersonnelService() { return keyPersonnelService; } public void setKradWorkflowDocumentService(WorkflowDocumentService kradWorkflowDocumentService) { this.kradWorkflowDocumentService = kradWorkflowDocumentService; } public void setKcDocumentRejectionService(KcDocumentRejectionService kcDocumentRejectionService) { this.kcDocumentRejectionService = kcDocumentRejectionService; } public PessimisticLockService getPessimisticLockService() { return pessimisticLockService; } public void setPessimisticLockService(PessimisticLockService pessimisticLockService) { this.pessimisticLockService = pessimisticLockService; } protected ProposalBudgetHierarchyService getProposalBudgetHierarchyService() { return proposalBudgetHierarchyService; } public void setProposalBudgetHierarchyService(ProposalBudgetHierarchyService proposalBudgetHierarchyService) { this.proposalBudgetHierarchyService = proposalBudgetHierarchyService; } }