/*
* 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;
}
}