/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * 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.kfs.module.bc.document.service.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import org.kuali.kfs.module.bc.BCConstants; import org.kuali.kfs.module.bc.BCConstants.LockStatus; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionFundingLock; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockStatus; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockSummary; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition; import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding; import org.kuali.kfs.module.bc.document.dataaccess.BudgetConstructionDao; import org.kuali.kfs.module.bc.document.dataaccess.BudgetConstructionLockDao; import org.kuali.kfs.module.bc.document.service.BudgetDocumentService; import org.kuali.kfs.module.bc.document.service.LockService; import org.kuali.kfs.module.bc.exception.BudgetConstructionLockUnavailableException; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.NonTransactional; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.krad.service.BusinessObjectService; import org.springframework.dao.DataAccessException; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * This class implements the LockService interface LockServiceImpl consists of methods that manage the various locks used in the * Budget module. Locks are needed to serialize user updates since a BC Edoc is potentially editable by many users simultaneously * and the default Optimistic locking scheme used by KFS would produce an inconsistent set of data. <B>Accountlock</B> controls * exclusive access to the BC Edoc <B>Positionlock</B> controls exclusive access to a BC Position <B>Fundinglock</B> controls * shared funding access. An associated Positionlock must exist before attempting to get a Fundinglock. Accountlock and Fundinglock * are mutex. <B>Transactionlock</B> controls exclusive access to serialize updates to the accounting lines in the BC Edoc. A * Fundinglock must exist before creating a Transactionlock. The Transactionlock lifecycle is short, required only for the duration * of the accounting line update. */ public class LockServiceImpl implements LockService { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LockServiceImpl.class); protected BudgetConstructionDao budgetConstructionDao; protected BudgetConstructionLockDao budgetConstructionLockDao; protected BudgetDocumentService budgetDocumentService; /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockAccount(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, * java.lang.String) */ @Transactional public BudgetConstructionLockStatus lockAccount(BudgetConstructionHeader bcHeader, String principalId) { BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); if (bcHeader != null) { bcLockStatus.setBudgetConstructionHeader(bcHeader); if (bcHeader.getBudgetLockUserIdentifier() == null) { bcHeader.setBudgetLockUserIdentifier(principalId); try { SpringContext.getBean(BusinessObjectService.class).save(bcHeader); bcLockStatus.setLockStatus(LockStatus.SUCCESS); } catch (DataAccessException ex) { bcLockStatus.setLockStatus(LockStatus.OPTIMISTIC_EX); } if (bcLockStatus.getLockStatus() == LockStatus.SUCCESS) { // need to check for funding locks for the account bcLockStatus.setFundingLocks(getFundingLocks(bcHeader)); if (!bcLockStatus.getFundingLocks().isEmpty()) { // get a freshcopy of header incase we lost the optimistic lock BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear()); unlockAccount(freshBcHeader); bcLockStatus.setBudgetConstructionHeader(freshBcHeader); bcLockStatus.setLockStatus(LockStatus.FLOCK_FOUND); } } return bcLockStatus; } else { if (bcHeader.getBudgetLockUserIdentifier().equals(principalId)) { bcLockStatus.setLockStatus(LockStatus.SUCCESS); return bcLockStatus; // the user already has a lock } else { bcLockStatus.setLockStatus(LockStatus.BY_OTHER); bcLockStatus.setAccountLockOwner(bcHeader.getBudgetLockUserIdentifier()); return bcLockStatus; // someone else has a lock } } } else { bcLockStatus.setLockStatus(LockStatus.NO_DOOR); return bcLockStatus; // budget header not found } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLocked(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding) */ @Transactional public boolean isAccountLocked(PendingBudgetConstructionAppointmentFunding appointmentFunding) { BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding); return this.isAccountLocked(budgetConstructionHeader); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLocked(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader) */ @Transactional public boolean isAccountLocked(BudgetConstructionHeader bcHeader) { BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear()); if (freshBcHeader != null) { return freshBcHeader.getBudgetLockUserIdentifier() != null; } else { return false; // unlikely, but not found still means not locked } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLockedByUser(java.lang.String, java.lang.String, * java.lang.String, java.lang.Integer, java.lang.String) */ @Transactional public boolean isAccountLockedByUser(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); if (freshBcHeader != null) { if (freshBcHeader.getBudgetLockUserIdentifier() != null && freshBcHeader.getBudgetLockUserIdentifier().equals(principalId)) { return true; } } return false; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockAccount(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader) */ @Transactional public LockStatus unlockAccount(BudgetConstructionHeader bcHeader) { LockStatus lockStatus; if (bcHeader != null) { if (bcHeader.getBudgetLockUserIdentifier() != null) { bcHeader.setBudgetLockUserIdentifier(null); try { SpringContext.getBean(BusinessObjectService.class).save(bcHeader); lockStatus = LockStatus.SUCCESS; } catch (DataAccessException ex) { lockStatus = LockStatus.OPTIMISTIC_EX; } } else { lockStatus = LockStatus.SUCCESS; // already unlocked } } else { lockStatus = LockStatus.NO_DOOR; // target not found } return lockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#getFundingLocks(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader) */ @Transactional public SortedSet<BudgetConstructionFundingLock> getFundingLocks(BudgetConstructionHeader bcHeader) { Collection<BudgetConstructionFundingLock> fundingLocks = budgetConstructionDao.getFlocksForAccount(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear()); SortedSet<BudgetConstructionFundingLock> sortedFundingLocks = new TreeSet<BudgetConstructionFundingLock>(new Comparator<BudgetConstructionFundingLock>() { public int compare(BudgetConstructionFundingLock aFlock, BudgetConstructionFundingLock bFlock) { String nameA = aFlock.getAppointmentFundingLockUser().getName(); String nameB = bFlock.getAppointmentFundingLockUser().getName(); return nameA.compareTo(nameB); } }); sortedFundingLocks.addAll(fundingLocks); return sortedFundingLocks; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockFunding(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, * java.lang.String) */ @Transactional public BudgetConstructionLockStatus lockFunding(BudgetConstructionHeader bcHeader, String principalId) { BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); if (!isAccountLocked(bcHeader)) { Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, bcHeader.getChartOfAccountsCode()); keys.put(KFSPropertyConstants.ACCOUNT_NUMBER, bcHeader.getAccountNumber()); keys.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, bcHeader.getSubAccountNumber()); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, bcHeader.getUniversityFiscalYear()); keys.put("appointmentFundingLockUserId", principalId); BudgetConstructionFundingLock budgetConstructionFundingLock = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionFundingLock.class, keys); if (budgetConstructionFundingLock != null && budgetConstructionFundingLock.getAppointmentFundingLockUserId().equals(principalId)) { bcLockStatus.setLockStatus(LockStatus.SUCCESS); } else { budgetConstructionFundingLock = new BudgetConstructionFundingLock(); budgetConstructionFundingLock.setAppointmentFundingLockUserId(principalId); budgetConstructionFundingLock.setAccountNumber(bcHeader.getAccountNumber()); budgetConstructionFundingLock.setSubAccountNumber(bcHeader.getSubAccountNumber()); budgetConstructionFundingLock.setChartOfAccountsCode(bcHeader.getChartOfAccountsCode()); budgetConstructionFundingLock.setUniversityFiscalYear(bcHeader.getUniversityFiscalYear()); budgetConstructionFundingLock.setFill1("L"); budgetConstructionFundingLock.setFill2("L"); budgetConstructionFundingLock.setFill3("L"); budgetConstructionFundingLock.setFill4("L"); budgetConstructionFundingLock.setFill5("L"); SpringContext.getBean(BusinessObjectService.class).save(budgetConstructionFundingLock); if (isAccountLocked(bcHeader)) { // unlikely, but need to check this bcLockStatus.setLockStatus(LockStatus.BY_OTHER); bcLockStatus.setAccountLockOwner(bcHeader.getBudgetLockUserIdentifier()); unlockFunding(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear(), principalId); } else { bcLockStatus.setLockStatus(LockStatus.SUCCESS); } } } else { bcLockStatus.setLockStatus(LockStatus.BY_OTHER); bcLockStatus.setAccountLockOwner(bcHeader.getBudgetLockUserIdentifier()); } return bcLockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, * org.kuali.rice.kim.api.identity.Person) */ @NonTransactional public BudgetConstructionLockStatus lockFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding); return this.lockFunding(budgetConstructionHeader, person.getPrincipalId()); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockFunding(java.lang.String, java.lang.String, java.lang.String, * java.lang.Integer, java.lang.String) */ @Transactional public LockStatus unlockFunding(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { LockStatus lockStatus; Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode); keys.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber); keys.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, subAccountNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); keys.put("appointmentFundingLockUserId", principalId); BudgetConstructionFundingLock budgetConstructionFundingLock = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionFundingLock.class, keys); if (budgetConstructionFundingLock != null) { budgetConstructionDao.deleteBudgetConstructionFundingLock(budgetConstructionFundingLock); lockStatus = LockStatus.SUCCESS; } else { lockStatus = LockStatus.NO_DOOR; // target not found } return lockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, * org.kuali.rice.kim.api.identity.Person) */ @Transactional public LockStatus unlockFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { Integer fiscalYear = appointmentFunding.getUniversityFiscalYear(); String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode(); String objectCode = appointmentFunding.getFinancialObjectCode(); String accountNumber = appointmentFunding.getAccountNumber(); String subAccountNumber = appointmentFunding.getSubAccountNumber(); return this.unlockFunding(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, person.getPrincipalId()); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockFunding(java.util.List, org.kuali.rice.kim.api.identity.Person) */ @Transactional public void unlockFunding(List<PendingBudgetConstructionAppointmentFunding> lockedFundings, Person person) { for (PendingBudgetConstructionAppointmentFunding appointmentFunding : lockedFundings) { this.unlockFunding(appointmentFunding, person); } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isFundingLockedByUser(java.lang.String, java.lang.String, * java.lang.String, java.lang.Integer, java.lang.String) */ @Transactional public boolean isFundingLockedByUser(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode); keys.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber); keys.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, subAccountNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); keys.put("appointmentFundingLockUserId", principalId); BudgetConstructionFundingLock budgetConstructionFundingLock = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionFundingLock.class, keys); if (budgetConstructionFundingLock != null) { return true; } return false; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockPosition(java.lang.String, java.lang.Integer, java.lang.String) */ @Transactional public BudgetConstructionLockStatus lockPosition(String positionNumber, Integer fiscalYear, String principalId) { BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.POSITION_NUMBER, positionNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); BudgetConstructionPosition bcPosition = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionPosition.class, keys); if (bcPosition != null) { if (bcPosition.getPositionLockUserIdentifier() == null) { bcPosition.setPositionLockUserIdentifier(principalId); try { SpringContext.getBean(BusinessObjectService.class).save(bcPosition); bcLockStatus.setLockStatus(LockStatus.SUCCESS); } catch (DataAccessException ex) { bcLockStatus.setLockStatus(LockStatus.OPTIMISTIC_EX); } return bcLockStatus; } else { if (bcPosition.getPositionLockUserIdentifier().equals(principalId)) { bcLockStatus.setLockStatus(LockStatus.SUCCESS); return bcLockStatus; // the user already has a lock } else { bcLockStatus.setLockStatus(LockStatus.BY_OTHER); bcLockStatus.setPositionLockOwner(bcPosition.getPositionLockUserIdentifier()); return bcLockStatus; // someone else has a lock } } } else { bcLockStatus.setLockStatus(LockStatus.NO_DOOR); return bcLockStatus; // position not found } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockPosition(org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition, * org.kuali.rice.kim.api.identity.Person) */ @Transactional public BudgetConstructionLockStatus lockPosition(BudgetConstructionPosition position, Person person) { String positionNumber = position.getPositionNumber(); Integer fiscalYear = position.getUniversityFiscalYear(); String principalId = person.getPrincipalId(); return this.lockPosition(positionNumber, fiscalYear, principalId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isPositionLocked(java.lang.String, java.lang.Integer) */ @Transactional public boolean isPositionLocked(String positionNumber, Integer fiscalYear) { Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.POSITION_NUMBER, positionNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); BudgetConstructionPosition bcPosition = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionPosition.class, keys); if (bcPosition != null) { if (bcPosition.getPositionLockUserIdentifier() != null) { return true; } else { return false; } } else { return false; // unlikely, but still means not locked } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isPositionLockedByUser(java.lang.String, java.lang.Integer, * java.lang.String) */ @Transactional public boolean isPositionLockedByUser(String positionNumber, Integer fiscalYear, String principalId) { Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.POSITION_NUMBER, positionNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); BudgetConstructionPosition bcPosition = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionPosition.class, keys); if (bcPosition != null && bcPosition.getPositionLockUserIdentifier() != null && bcPosition.getPositionLockUserIdentifier().equals(principalId)) { return true; } return false; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isPositionFundingLockedByUser(java.lang.String, java.lang.String, * java.lang.String, java.lang.String, java.lang.Integer, java.lang.String) */ @Transactional public boolean isPositionFundingLockedByUser(String positionNumber, String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { return this.isPositionLockedByUser(positionNumber, fiscalYear, principalId) && this.isFundingLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPosition(java.lang.String, java.lang.Integer) */ @Transactional public LockStatus unlockPosition(String positionNumber, Integer fiscalYear) { LockStatus lockStatus; Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.POSITION_NUMBER, positionNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); BudgetConstructionPosition bcPosition = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionPosition.class, keys); if (bcPosition != null) { if (bcPosition.getPositionLockUserIdentifier() != null) { bcPosition.setPositionLockUserIdentifier(null); try { SpringContext.getBean(BusinessObjectService.class).save(bcPosition); lockStatus = LockStatus.SUCCESS; } catch (DataAccessException ex) { lockStatus = LockStatus.OPTIMISTIC_EX; } } else { lockStatus = LockStatus.SUCCESS; // already unlocked } } else { lockStatus = LockStatus.NO_DOOR; // target not found } return lockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPosition(java.lang.String, java.lang.Integer, * java.lang.String) */ @Transactional public LockStatus unlockPosition(String positionNumber, Integer fiscalYear, String principalId) { Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.POSITION_NUMBER, positionNumber); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); BudgetConstructionPosition bcPosition = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionPosition.class, keys); if (bcPosition == null || !principalId.equals(bcPosition.getPositionLockUserIdentifier())) { return LockStatus.NO_DOOR; } try { bcPosition.setPositionLockUserIdentifier(null); SpringContext.getBean(BusinessObjectService.class).save(bcPosition); return LockStatus.SUCCESS; } catch (DataAccessException ex) { return LockStatus.OPTIMISTIC_EX; } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPostion(org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition, * org.kuali.rice.kim.api.identity.Person) */ @Transactional public LockStatus unlockPostion(BudgetConstructionPosition position, Person person) { Integer fiscalYear = position.getUniversityFiscalYear(); String positionNumber = position.getPositionNumber(); return this.unlockPosition(positionNumber, fiscalYear, person.getPrincipalId()); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPostion(java.util.List, org.kuali.rice.kim.api.identity.Person) */ @Transactional public void unlockPostion(List<BudgetConstructionPosition> lockedPositions, Person person) { for (BudgetConstructionPosition position : lockedPositions) { this.unlockPostion(position, person); } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockTransaction(java.lang.String, java.lang.String, * java.lang.String, java.lang.Integer, java.lang.String) */ @Transactional public BudgetConstructionLockStatus lockTransaction(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { int lockRetry = 1; boolean done = false; BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); // gwp - 12/9/2008 Adding extra if test to handle a current transaction lock by the user // as a successful lock even though this is not how Uniface handled it. // The old FIS kept track of the issued locks and only called this once // even when multiple BCAF rows were being updated and saved if (this.isTransactionLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId)) { bcLockStatus.setLockStatus(LockStatus.SUCCESS); done = true; } while (!done) { BudgetConstructionHeader bcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); if (bcHeader != null) { if (bcHeader.getBudgetTransactionLockUserIdentifier() == null) { bcHeader.setBudgetTransactionLockUserIdentifier(principalId); try { SpringContext.getBean(BusinessObjectService.class).save(bcHeader); bcLockStatus.setLockStatus(LockStatus.SUCCESS); } catch (DataAccessException ex) { bcLockStatus.setLockStatus(LockStatus.OPTIMISTIC_EX); // unlikely } done = true; } else { if (lockRetry > BCConstants.maxLockRetry) { bcLockStatus.setLockStatus(LockStatus.BY_OTHER); bcLockStatus.setTransactionLockOwner(bcHeader.getBudgetTransactionLockUserIdentifier()); done = true; } lockRetry++; // someone else has a lock, retry } } else { bcLockStatus.setLockStatus(LockStatus.NO_DOOR); // target not found, unlikely done = true; } } return bcLockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockTransaction(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, * org.kuali.rice.kim.api.identity.Person) */ @Transactional public BudgetConstructionLockStatus lockTransaction(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode(); String accountNumber = appointmentFunding.getAccountNumber(); String subAccountNumber = appointmentFunding.getSubAccountNumber(); Integer fiscalYear = appointmentFunding.getUniversityFiscalYear(); return this.lockTransaction(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, person.getPrincipalId()); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isTransactionLocked(java.lang.String, java.lang.String, * java.lang.String, java.lang.Integer) */ @Transactional public boolean isTransactionLocked(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear) { BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); if (freshBcHeader != null) { if (freshBcHeader.getBudgetTransactionLockUserIdentifier() != null) { return true; } else { return false; } } else { return false; // unlikely, but still means not locked } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isTransactionLockedByUser(java.lang.String, java.lang.String, * java.lang.String, java.lang.Integer, java.lang.String) */ @Transactional public boolean isTransactionLockedByUser(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); if (freshBcHeader != null && freshBcHeader.getBudgetTransactionLockUserIdentifier() != null && freshBcHeader.getBudgetTransactionLockUserIdentifier().equals(principalId)) { return true; } return false; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockTransaction(java.lang.String, java.lang.String, * java.lang.String, java.lang.Integer) */ @Transactional public LockStatus unlockTransaction(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear) { LockStatus lockStatus = LockStatus.NO_DOOR; BudgetConstructionHeader bcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); if (bcHeader != null) { if (bcHeader.getBudgetTransactionLockUserIdentifier() != null) { bcHeader.setBudgetTransactionLockUserIdentifier(null); try { SpringContext.getBean(BusinessObjectService.class).save(bcHeader); lockStatus = LockStatus.SUCCESS; } catch (DataAccessException ex) { lockStatus = LockStatus.OPTIMISTIC_EX; } } else { lockStatus = LockStatus.SUCCESS; // already unlocked } } else { lockStatus = LockStatus.NO_DOOR; // target not found } return lockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockTransaction(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, * org.kuali.rice.kim.api.identity.Person) */ @Transactional public void unlockTransaction(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode(); String accountNumber = appointmentFunding.getAccountNumber(); String subAccountNumber = appointmentFunding.getSubAccountNumber(); Integer fiscalYear = appointmentFunding.getUniversityFiscalYear(); String principalId = person.getPrincipalId(); if (this.isTransactionLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId)) { this.unlockTransaction(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); } } /** * @see org.kuali.kfs.module.bc.document.service.LockService#getAllAccountLocks(String lockUserId) */ @Transactional public List<BudgetConstructionHeader> getAllAccountLocks(String lockUserId) { return budgetConstructionLockDao.getAllAccountLocks(lockUserId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#getAllFundLocks(String lockUserId) */ @Transactional public List<BudgetConstructionFundingLock> getOrphanedFundingLocks(String lockUserId) { return budgetConstructionLockDao.getOrphanedFundingLocks(lockUserId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#getOrphanedPositionLocks(String lockUserId) */ @Transactional public List<BudgetConstructionPosition> getOrphanedPositionLocks(String lockUserId) { return budgetConstructionLockDao.getOrphanedPositionLocks(lockUserId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#getAllTransactionLocks(String lockUserId) */ @Transactional public List<BudgetConstructionHeader> getAllTransactionLocks(String lockUserId) { return budgetConstructionLockDao.getAllTransactionLocks(lockUserId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#getAllPositionFundingLocks(java.lang.String) */ @Transactional public List<PendingBudgetConstructionAppointmentFunding> getAllPositionFundingLocks(String lockUserId) { return budgetConstructionLockDao.getAllPositionFundingLocks(lockUserId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#checkLockExists(org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockSummary) */ @Transactional public boolean checkLockExists(BudgetConstructionLockSummary lockSummary) { String lockType = lockSummary.getLockType(); if (BCConstants.LockTypes.ACCOUNT_LOCK.equals(lockType)) { return this.isAccountLockedByUser(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } if (BCConstants.LockTypes.TRANSACTION_LOCK.equals(lockType)) { return this.isTransactionLockedByUser(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } if (BCConstants.LockTypes.FUNDING_LOCK.equals(lockType)) { return this.isFundingLockedByUser(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } if (BCConstants.LockTypes.POSITION_LOCK.equals(lockType)) { return this.isPositionLockedByUser(lockSummary.getPositionNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } if (BCConstants.LockTypes.POSITION_FUNDING_LOCK.equals(lockType)) { return this.isPositionFundingLockedByUser(lockSummary.getPositionNumber(), lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } return false; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#doUnlock(org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockSummary) */ @Transactional public LockStatus doUnlock(BudgetConstructionLockSummary lockSummary) { String lockType = lockSummary.getLockType(); if (BCConstants.LockTypes.ACCOUNT_LOCK.equals(lockType)) { BudgetConstructionHeader bcHeader = budgetConstructionDao.getByCandidateKey(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear()); if (bcHeader != null) { return this.unlockAccount(bcHeader); } } if (BCConstants.LockTypes.TRANSACTION_LOCK.equals(lockType)) { return this.unlockTransaction(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear()); } if (BCConstants.LockTypes.FUNDING_LOCK.equals(lockType)) { return this.unlockFunding(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } if (BCConstants.LockTypes.POSITION_LOCK.equals(lockType)) { return this.unlockPosition(lockSummary.getPositionNumber(), lockSummary.getUniversityFiscalYear()); } if (BCConstants.LockTypes.POSITION_FUNDING_LOCK.equals(lockType)) { Map<String, Object> keys = new HashMap<String, Object>(); keys.put(KFSPropertyConstants.POSITION_NUMBER, lockSummary.getPositionNumber()); keys.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, lockSummary.getUniversityFiscalYear()); BudgetConstructionPosition position = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(BudgetConstructionPosition.class, keys); for (PendingBudgetConstructionAppointmentFunding appointmentFunding : position.getPendingBudgetConstructionAppointmentFunding()) { this.unlockFunding(appointmentFunding.getChartOfAccountsCode(), appointmentFunding.getAccountNumber(), appointmentFunding.getSubAccountNumber(), appointmentFunding.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); } return this.unlockPosition(position.getPositionNumber(), position.getUniversityFiscalYear()); } return LockStatus.NO_DOOR; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLockedByUser(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, * org.kuali.rice.kim.api.identity.Person) */ @Transactional public boolean isAccountLockedByUser(BudgetConstructionHeader budgetConstructionHeader, Person person) { String chartOfAccountsCode = budgetConstructionHeader.getChartOfAccountsCode(); String accountNumber = budgetConstructionHeader.getAccountNumber(); String subAccountNumber = budgetConstructionHeader.getSubAccountNumber(); Integer fiscalYear = budgetConstructionHeader.getUniversityFiscalYear(); return this.isAccountLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, person.getPrincipalId()); } @NonTransactional public void setBudgetConstructionDao(BudgetConstructionDao bcHeaderDao) { this.budgetConstructionDao = bcHeaderDao; } /** * Sets the budgetConstructionLockDao attribute value. * * @param budgetConstructionLockDao The budgetConstructionLockDao to set. */ @NonTransactional public void setBudgetConstructionLockDao(BudgetConstructionLockDao budgetConstructionLockDao) { this.budgetConstructionLockDao = budgetConstructionLockDao; } /** * Sets the budgetDocumentService attribute value. * * @param budgetDocumentService The budgetDocumentService to set. */ @NonTransactional public void setBudgetDocumentService(BudgetDocumentService budgetDocumentService) { this.budgetDocumentService = budgetDocumentService; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockPendingBudgetConstructionAppointmentFundingRecords(java.util.List, * org.kuali.rice.kim.api.identity.Person) */ @Transactional(propagation = Propagation.REQUIRES_NEW) public List<PendingBudgetConstructionAppointmentFunding> lockPendingBudgetConstructionAppointmentFundingRecords(List<PendingBudgetConstructionAppointmentFunding> fundingRecords, Person user) throws BudgetConstructionLockUnavailableException { List<PendingBudgetConstructionAppointmentFunding> lockedFundingRecords = new ArrayList<PendingBudgetConstructionAppointmentFunding>(); Map<String, PendingBudgetConstructionAppointmentFunding> lockMap = new HashMap<String, PendingBudgetConstructionAppointmentFunding>(); for (PendingBudgetConstructionAppointmentFunding fundingRecord : fundingRecords) { BudgetConstructionHeader header = budgetDocumentService.getBudgetConstructionHeader(fundingRecord); String lockingKey = fundingRecord.getUniversityFiscalYear() + "-" + fundingRecord.getChartOfAccountsCode() + "-" + fundingRecord.getAccountNumber() + "-" + fundingRecord.getSubAccountNumber(); if (!lockMap.containsKey(lockingKey)) { BudgetConstructionLockStatus lockStatus = lockAccount(header, user.getPrincipalId()); if (lockStatus.getLockStatus().equals(BCConstants.LockStatus.BY_OTHER)) { throw new BudgetConstructionLockUnavailableException(lockStatus); } else if (lockStatus.getLockStatus().equals(BCConstants.LockStatus.FLOCK_FOUND)) { throw new BudgetConstructionLockUnavailableException(lockStatus); } else if (!lockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { throw new BudgetConstructionLockUnavailableException(lockStatus); } else { lockMap.put(lockingKey, fundingRecord); lockedFundingRecords.add(fundingRecord); } } } return lockedFundingRecords; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockAccountAndCommit(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, * java.lang.String) */ @Transactional(propagation = Propagation.REQUIRES_NEW) public BudgetConstructionLockStatus lockAccountAndCommit(BudgetConstructionHeader bcHeader, String principalId) { return lockAccount(bcHeader, principalId); } /** * @see org.kuali.kfs.module.bc.document.service.LockService#lockPositionAndActiveFunding(java.lang.Integer, java.lang.String, * java.lang.String) */ @Transactional(propagation = Propagation.REQUIRES_NEW) public BudgetConstructionLockStatus lockPositionAndActiveFunding(Integer universityFiscalYear, String positionNumber, String principalId) { // attempt to lock position first BudgetConstructionLockStatus lockStatus = lockPosition(positionNumber, universityFiscalYear, principalId); if (!lockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { return lockStatus; } // retrieve funding records for the position List<PendingBudgetConstructionAppointmentFunding> allPositionFunding = budgetConstructionDao.getAllFundingForPosition(universityFiscalYear, positionNumber); // lock funding if not marked as delete for (PendingBudgetConstructionAppointmentFunding appointmentFunding : allPositionFunding) { if (!appointmentFunding.isAppointmentFundingDeleteIndicator()) { BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding); BudgetConstructionLockStatus fundingLockStatus = lockFunding(budgetConstructionHeader, principalId); if (!fundingLockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { return lockStatus; } } } // successfully obtained all locks BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); bcLockStatus.setLockStatus(LockStatus.SUCCESS); return bcLockStatus; } /** * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPositionAndActiveFunding(java.lang.Integer, java.lang.String, * java.lang.String) */ @Transactional(propagation = Propagation.REQUIRES_NEW) public LockStatus unlockPositionAndActiveFunding(Integer universityFiscalYear, String positionNumber, String principalId) { // unlock position LockStatus lockStatus = unlockPosition(positionNumber, universityFiscalYear, principalId); if (!lockStatus.equals(BCConstants.LockStatus.SUCCESS)) { return lockStatus; } // retrieve funding records for the position List<PendingBudgetConstructionAppointmentFunding> allPositionFunding = budgetConstructionDao.getAllFundingForPosition(universityFiscalYear, positionNumber); // unlock funding if not marked as delete for (PendingBudgetConstructionAppointmentFunding appointmentFunding : allPositionFunding) { if (!appointmentFunding.isAppointmentFundingDeleteIndicator()) { LockStatus fundingLockStatus = unlockFunding(appointmentFunding.getChartOfAccountsCode(), appointmentFunding.getAccountNumber(), appointmentFunding.getSubAccountNumber(), appointmentFunding.getUniversityFiscalYear(), principalId); if (!fundingLockStatus.equals(BCConstants.LockStatus.SUCCESS)) { return lockStatus; } } } // successfully completed unlocks return LockStatus.SUCCESS; } }