001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.module.bc.document.service.impl; 017 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.Comparator; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.SortedSet; 025 import java.util.TreeSet; 026 027 import org.kuali.kfs.module.bc.BCConstants; 028 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionFundingLock; 029 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader; 030 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockStatus; 031 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockSummary; 032 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition; 033 import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding; 034 import org.kuali.kfs.module.bc.document.dataaccess.BudgetConstructionDao; 035 import org.kuali.kfs.module.bc.document.dataaccess.BudgetConstructionLockDao; 036 import org.kuali.kfs.module.bc.document.service.BudgetDocumentService; 037 import org.kuali.kfs.module.bc.document.service.LockService; 038 import org.kuali.kfs.module.bc.exception.BudgetConstructionLockUnavailableException; 039 import org.kuali.kfs.module.bc.BCConstants; 040 import org.kuali.kfs.module.bc.BCConstants.LockStatus; 041 import org.kuali.kfs.sys.service.NonTransactional; 042 import org.kuali.rice.kim.bo.Person; 043 import org.springframework.dao.DataAccessException; 044 import org.springframework.transaction.annotation.Propagation; 045 import org.springframework.transaction.annotation.Transactional; 046 047 /** 048 * This class implements the LockService interface LockServiceImpl consists of methods that manage the various locks used in the 049 * Budget module. Locks are needed to serialize user updates since a BC Edoc is potentially editable by many users simultaneously 050 * and the default Optimistic locking scheme used by KFS would produce an inconsistent set of data. <B>Accountlock</B> controls 051 * exclusive access to the BC Edoc <B>Positionlock</B> controls exclusive access to a BC Position <B>Fundinglock</B> controls 052 * shared funding access. An associated Positionlock must exist before attempting to get a Fundinglock. Accountlock and Fundinglock 053 * are mutex. <B>Transactionlock</B> controls exclusive access to serialize updates to the accounting lines in the BC Edoc. A 054 * Fundinglock must exist before creating a Transactionlock. The Transactionlock lifecycle is short, required only for the duration 055 * of the accounting line update. 056 */ 057 public class LockServiceImpl implements LockService { 058 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LockServiceImpl.class); 059 060 private BudgetConstructionDao budgetConstructionDao; 061 private BudgetConstructionLockDao budgetConstructionLockDao; 062 private BudgetDocumentService budgetDocumentService; 063 064 /** 065 * @see org.kuali.kfs.module.bc.document.service.LockService#lockAccount(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, 066 * java.lang.String) 067 */ 068 @Transactional 069 public BudgetConstructionLockStatus lockAccount(BudgetConstructionHeader bcHeader, String principalId) { 070 071 BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); 072 if (bcHeader != null) { 073 bcLockStatus.setBudgetConstructionHeader(bcHeader); 074 if (bcHeader.getBudgetLockUserIdentifier() == null) { 075 bcHeader.setBudgetLockUserIdentifier(principalId); 076 try { 077 budgetConstructionDao.saveBudgetConstructionHeader(bcHeader); 078 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 079 } 080 catch (DataAccessException ex) { 081 bcLockStatus.setLockStatus(LockStatus.OPTIMISTIC_EX); 082 } 083 if (bcLockStatus.getLockStatus() == LockStatus.SUCCESS) { 084 // need to check for funding locks for the account 085 bcLockStatus.setFundingLocks(getFundingLocks(bcHeader)); 086 if (!bcLockStatus.getFundingLocks().isEmpty()) { 087 // get a freshcopy of header incase we lost the optimistic lock 088 BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear()); 089 unlockAccount(freshBcHeader); 090 bcLockStatus.setBudgetConstructionHeader(freshBcHeader); 091 bcLockStatus.setLockStatus(LockStatus.FLOCK_FOUND); 092 } 093 } 094 return bcLockStatus; 095 } 096 else { 097 if (bcHeader.getBudgetLockUserIdentifier().equals(principalId)) { 098 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 099 return bcLockStatus; // the user already has a lock 100 } 101 else { 102 bcLockStatus.setLockStatus(LockStatus.BY_OTHER); 103 bcLockStatus.setAccountLockOwner(bcHeader.getBudgetLockUserIdentifier()); 104 return bcLockStatus; // someone else has a lock 105 } 106 } 107 } 108 else { 109 bcLockStatus.setLockStatus(LockStatus.NO_DOOR); 110 return bcLockStatus; // budget header not found 111 } 112 } 113 114 /** 115 * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLocked(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding) 116 */ 117 @Transactional 118 public boolean isAccountLocked(PendingBudgetConstructionAppointmentFunding appointmentFunding) { 119 BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding); 120 121 return this.isAccountLocked(budgetConstructionHeader); 122 } 123 124 /** 125 * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLocked(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader) 126 */ 127 @Transactional 128 public boolean isAccountLocked(BudgetConstructionHeader bcHeader) { 129 130 BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear()); 131 if (freshBcHeader != null) { 132 return freshBcHeader.getBudgetLockUserIdentifier() != null; 133 } 134 else { 135 return false; // unlikely, but not found still means not locked 136 } 137 } 138 139 /** 140 * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLockedByUser(java.lang.String, java.lang.String, 141 * java.lang.String, java.lang.Integer, java.lang.String) 142 */ 143 @Transactional 144 public boolean isAccountLockedByUser(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { 145 BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); 146 if (freshBcHeader != null) { 147 if (freshBcHeader.getBudgetLockUserIdentifier() != null && freshBcHeader.getBudgetLockUserIdentifier().equals(principalId)) { 148 return true; 149 } 150 } 151 152 return false; 153 } 154 155 /** 156 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockAccount(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader) 157 */ 158 @Transactional 159 public LockStatus unlockAccount(BudgetConstructionHeader bcHeader) { 160 161 LockStatus lockStatus; 162 163 if (bcHeader != null) { 164 if (bcHeader.getBudgetLockUserIdentifier() != null) { 165 bcHeader.setBudgetLockUserIdentifier(null); 166 try { 167 budgetConstructionDao.saveBudgetConstructionHeader(bcHeader); 168 lockStatus = LockStatus.SUCCESS; 169 } 170 catch (DataAccessException ex) { 171 lockStatus = LockStatus.OPTIMISTIC_EX; 172 } 173 } 174 else { 175 lockStatus = LockStatus.SUCCESS; // already unlocked 176 } 177 } 178 else { 179 lockStatus = LockStatus.NO_DOOR; // target not found 180 } 181 return lockStatus; 182 } 183 184 /** 185 * @see org.kuali.kfs.module.bc.document.service.LockService#getFundingLocks(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader) 186 */ 187 @Transactional 188 public SortedSet<BudgetConstructionFundingLock> getFundingLocks(BudgetConstructionHeader bcHeader) { 189 190 Collection<BudgetConstructionFundingLock> fundingLocks = budgetConstructionDao.getFlocksForAccount(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear()); 191 SortedSet<BudgetConstructionFundingLock> sortedFundingLocks = new TreeSet<BudgetConstructionFundingLock>(new Comparator<BudgetConstructionFundingLock>() { 192 public int compare(BudgetConstructionFundingLock aFlock, BudgetConstructionFundingLock bFlock) { 193 String nameA = aFlock.getAppointmentFundingLockUser().getName(); 194 String nameB = bFlock.getAppointmentFundingLockUser().getName(); 195 return nameA.compareTo(nameB); 196 } 197 }); 198 199 sortedFundingLocks.addAll(fundingLocks); 200 return sortedFundingLocks; 201 } 202 203 /** 204 * @see org.kuali.kfs.module.bc.document.service.LockService#lockFunding(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, 205 * java.lang.String) 206 */ 207 @Transactional 208 public BudgetConstructionLockStatus lockFunding(BudgetConstructionHeader bcHeader, String principalId) { 209 210 BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); 211 212 if (!isAccountLocked(bcHeader)) { 213 BudgetConstructionFundingLock budgetConstructionFundingLock = budgetConstructionDao.getByPrimaryId(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear(), principalId); 214 if (budgetConstructionFundingLock != null && budgetConstructionFundingLock.getAppointmentFundingLockUserId().equals(principalId)) { 215 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 216 } 217 else { 218 budgetConstructionFundingLock = new BudgetConstructionFundingLock(); 219 budgetConstructionFundingLock.setAppointmentFundingLockUserId(principalId); 220 budgetConstructionFundingLock.setAccountNumber(bcHeader.getAccountNumber()); 221 budgetConstructionFundingLock.setSubAccountNumber(bcHeader.getSubAccountNumber()); 222 budgetConstructionFundingLock.setChartOfAccountsCode(bcHeader.getChartOfAccountsCode()); 223 budgetConstructionFundingLock.setUniversityFiscalYear(bcHeader.getUniversityFiscalYear()); 224 budgetConstructionFundingLock.setFill1("L"); 225 budgetConstructionFundingLock.setFill2("L"); 226 budgetConstructionFundingLock.setFill3("L"); 227 budgetConstructionFundingLock.setFill4("L"); 228 budgetConstructionFundingLock.setFill5("L"); 229 budgetConstructionDao.saveBudgetConstructionFundingLock(budgetConstructionFundingLock); 230 if (isAccountLocked(bcHeader)) { // unlikely, but need to check this 231 bcLockStatus.setLockStatus(LockStatus.BY_OTHER); 232 unlockFunding(bcHeader.getChartOfAccountsCode(), bcHeader.getAccountNumber(), bcHeader.getSubAccountNumber(), bcHeader.getUniversityFiscalYear(), principalId); 233 } 234 else { 235 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 236 } 237 } 238 } 239 else { 240 bcLockStatus.setLockStatus(LockStatus.BY_OTHER); 241 } 242 return bcLockStatus; 243 } 244 245 /** 246 * @see org.kuali.kfs.module.bc.document.service.LockService#lockFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, 247 * org.kuali.rice.kim.bo.Person) 248 */ 249 @NonTransactional 250 public BudgetConstructionLockStatus lockFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { 251 BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding); 252 253 return this.lockFunding(budgetConstructionHeader, person.getPrincipalId()); 254 } 255 256 /** 257 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockFunding(java.lang.String, java.lang.String, java.lang.String, 258 * java.lang.Integer, java.lang.String) 259 */ 260 @Transactional 261 public LockStatus unlockFunding(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { 262 263 LockStatus lockStatus; 264 265 BudgetConstructionFundingLock budgetConstructionFundingLock = budgetConstructionDao.getByPrimaryId(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId); 266 if (budgetConstructionFundingLock != null) { 267 budgetConstructionDao.deleteBudgetConstructionFundingLock(budgetConstructionFundingLock); 268 lockStatus = LockStatus.SUCCESS; 269 } 270 else { 271 lockStatus = LockStatus.NO_DOOR; // target not found 272 } 273 return lockStatus; 274 } 275 276 /** 277 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, 278 * org.kuali.rice.kim.bo.Person) 279 */ 280 @Transactional 281 public LockStatus unlockFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { 282 Integer fiscalYear = appointmentFunding.getUniversityFiscalYear(); 283 String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode(); 284 String objectCode = appointmentFunding.getFinancialObjectCode(); 285 String accountNumber = appointmentFunding.getAccountNumber(); 286 String subAccountNumber = appointmentFunding.getSubAccountNumber(); 287 288 return this.unlockFunding(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, person.getPrincipalId()); 289 } 290 291 /** 292 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockFunding(java.util.List, org.kuali.rice.kim.bo.Person) 293 */ 294 @Transactional 295 public void unlockFunding(List<PendingBudgetConstructionAppointmentFunding> lockedFundings, Person person) { 296 for (PendingBudgetConstructionAppointmentFunding appointmentFunding : lockedFundings) { 297 this.unlockFunding(appointmentFunding, person); 298 } 299 } 300 301 /** 302 * @see org.kuali.kfs.module.bc.document.service.LockService#isFundingLockedByUser(java.lang.String, java.lang.String, 303 * java.lang.String, java.lang.Integer, java.lang.String) 304 */ 305 @Transactional 306 public boolean isFundingLockedByUser(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { 307 BudgetConstructionFundingLock budgetConstructionFundingLock = budgetConstructionDao.getByPrimaryId(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId); 308 if (budgetConstructionFundingLock != null) { 309 return true; 310 } 311 312 return false; 313 } 314 315 /** 316 * @see org.kuali.kfs.module.bc.document.service.LockService#lockPosition(java.lang.String, java.lang.Integer, java.lang.String) 317 */ 318 @Transactional 319 public BudgetConstructionLockStatus lockPosition(String positionNumber, Integer fiscalYear, String principalId) { 320 321 BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); 322 BudgetConstructionPosition bcPosition = budgetConstructionDao.getByPrimaryId(positionNumber, fiscalYear); 323 if (bcPosition != null) { 324 if (bcPosition.getPositionLockUserIdentifier() == null) { 325 bcPosition.setPositionLockUserIdentifier(principalId); 326 try { 327 budgetConstructionDao.saveBudgetConstructionPosition(bcPosition); 328 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 329 } 330 catch (DataAccessException ex) { 331 bcLockStatus.setLockStatus(LockStatus.OPTIMISTIC_EX); 332 } 333 return bcLockStatus; 334 } 335 else { 336 if (bcPosition.getPositionLockUserIdentifier().equals(principalId)) { 337 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 338 return bcLockStatus; // the user already has a lock 339 } 340 else { 341 bcLockStatus.setLockStatus(LockStatus.BY_OTHER); 342 bcLockStatus.setPositionLockOwner(bcPosition.getPositionLockUserIdentifier()); 343 return bcLockStatus; // someone else has a lock 344 } 345 } 346 } 347 else { 348 bcLockStatus.setLockStatus(LockStatus.NO_DOOR); 349 return bcLockStatus; // position not found 350 } 351 } 352 353 /** 354 * @see org.kuali.kfs.module.bc.document.service.LockService#lockPosition(org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition, 355 * org.kuali.rice.kim.bo.Person) 356 */ 357 @Transactional 358 public BudgetConstructionLockStatus lockPosition(BudgetConstructionPosition position, Person person) { 359 String positionNumber = position.getPositionNumber(); 360 Integer fiscalYear = position.getUniversityFiscalYear(); 361 String principalId = person.getPrincipalId(); 362 363 return this.lockPosition(positionNumber, fiscalYear, principalId); 364 } 365 366 /** 367 * @see org.kuali.kfs.module.bc.document.service.LockService#isPositionLocked(java.lang.String, java.lang.Integer) 368 */ 369 @Transactional 370 public boolean isPositionLocked(String positionNumber, Integer fiscalYear) { 371 372 BudgetConstructionPosition bcPosition = budgetConstructionDao.getByPrimaryId(positionNumber, fiscalYear); 373 if (bcPosition != null) { 374 if (bcPosition.getPositionLockUserIdentifier() != null) { 375 return true; 376 } 377 else { 378 return false; 379 } 380 } 381 else { 382 return false; // unlikely, but still means not locked 383 } 384 } 385 386 /** 387 * @see org.kuali.kfs.module.bc.document.service.LockService#isPositionLockedByUser(java.lang.String, java.lang.Integer, 388 * java.lang.String) 389 */ 390 @Transactional 391 public boolean isPositionLockedByUser(String positionNumber, Integer fiscalYear, String principalId) { 392 BudgetConstructionPosition bcPosition = budgetConstructionDao.getByPrimaryId(positionNumber, fiscalYear); 393 if (bcPosition != null && bcPosition.getPositionLockUserIdentifier() != null && bcPosition.getPositionLockUserIdentifier().equals(principalId)) { 394 return true; 395 } 396 397 return false; 398 } 399 400 /** 401 * @see org.kuali.kfs.module.bc.document.service.LockService#isPositionFundingLockedByUser(java.lang.String, java.lang.String, 402 * java.lang.String, java.lang.String, java.lang.Integer, java.lang.String) 403 */ 404 @Transactional 405 public boolean isPositionFundingLockedByUser(String positionNumber, String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { 406 return this.isPositionLockedByUser(positionNumber, fiscalYear, principalId) && this.isFundingLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId); 407 } 408 409 /** 410 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPosition(java.lang.String, java.lang.Integer) 411 */ 412 @Transactional 413 public LockStatus unlockPosition(String positionNumber, Integer fiscalYear) { 414 415 LockStatus lockStatus; 416 417 BudgetConstructionPosition bcPosition = budgetConstructionDao.getByPrimaryId(positionNumber, fiscalYear); 418 if (bcPosition != null) { 419 if (bcPosition.getPositionLockUserIdentifier() != null) { 420 bcPosition.setPositionLockUserIdentifier(null); 421 try { 422 budgetConstructionDao.saveBudgetConstructionPosition(bcPosition); 423 lockStatus = LockStatus.SUCCESS; 424 } 425 catch (DataAccessException ex) { 426 lockStatus = LockStatus.OPTIMISTIC_EX; 427 } 428 } 429 else { 430 lockStatus = LockStatus.SUCCESS; // already unlocked 431 } 432 } 433 else { 434 lockStatus = LockStatus.NO_DOOR; // target not found 435 } 436 return lockStatus; 437 } 438 439 /** 440 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPosition(java.lang.String, java.lang.Integer, 441 * java.lang.String) 442 */ 443 @Transactional 444 public LockStatus unlockPosition(String positionNumber, Integer fiscalYear, String principalId) { 445 BudgetConstructionPosition bcPosition = budgetConstructionDao.getByPrimaryId(positionNumber, fiscalYear); 446 if (bcPosition == null || !principalId.equals(bcPosition.getPositionLockUserIdentifier())) { 447 return LockStatus.NO_DOOR; 448 } 449 450 try { 451 bcPosition.setPositionLockUserIdentifier(null); 452 budgetConstructionDao.saveBudgetConstructionPosition(bcPosition); 453 454 return LockStatus.SUCCESS; 455 } 456 catch (DataAccessException ex) { 457 return LockStatus.OPTIMISTIC_EX; 458 } 459 } 460 461 /** 462 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPostion(org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition, 463 * org.kuali.rice.kim.bo.Person) 464 */ 465 @Transactional 466 public LockStatus unlockPostion(BudgetConstructionPosition position, Person person) { 467 Integer fiscalYear = position.getUniversityFiscalYear(); 468 String positionNumber = position.getPositionNumber(); 469 470 return this.unlockPosition(positionNumber, fiscalYear, person.getPrincipalId()); 471 } 472 473 /** 474 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPostion(java.util.List, org.kuali.rice.kim.bo.Person) 475 */ 476 @Transactional 477 public void unlockPostion(List<BudgetConstructionPosition> lockedPositions, Person person) { 478 for (BudgetConstructionPosition position : lockedPositions) { 479 this.unlockPostion(position, person); 480 } 481 } 482 483 /** 484 * @see org.kuali.kfs.module.bc.document.service.LockService#lockTransaction(java.lang.String, java.lang.String, 485 * java.lang.String, java.lang.Integer, java.lang.String) 486 */ 487 @Transactional 488 public BudgetConstructionLockStatus lockTransaction(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { 489 490 int lockRetry = 1; 491 boolean done = false; 492 493 BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); 494 495 // gwp - 12/9/2008 Adding extra if test to handle a current transaction lock by the user 496 // as a successful lock even though this is not how Uniface handled it. 497 // The old FIS kept track of the issued locks and only called this once 498 // even when multiple BCAF rows were being updated and saved 499 if (this.isTransactionLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId)) { 500 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 501 done = true; 502 } 503 504 while (!done) { 505 BudgetConstructionHeader bcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); 506 if (bcHeader != null) { 507 if (bcHeader.getBudgetTransactionLockUserIdentifier() == null) { 508 bcHeader.setBudgetTransactionLockUserIdentifier(principalId); 509 try { 510 budgetConstructionDao.saveBudgetConstructionHeader(bcHeader); 511 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 512 } 513 catch (DataAccessException ex) { 514 bcLockStatus.setLockStatus(LockStatus.OPTIMISTIC_EX); // unlikely 515 } 516 done = true; 517 } 518 else { 519 if (lockRetry > BCConstants.maxLockRetry) { 520 bcLockStatus.setLockStatus(LockStatus.BY_OTHER); 521 bcLockStatus.setTransactionLockOwner(bcHeader.getBudgetTransactionLockUserIdentifier()); 522 done = true; 523 } 524 lockRetry++; // someone else has a lock, retry 525 } 526 } 527 else { 528 bcLockStatus.setLockStatus(LockStatus.NO_DOOR); // target not found, unlikely 529 done = true; 530 } 531 } 532 return bcLockStatus; 533 } 534 535 /** 536 * @see org.kuali.kfs.module.bc.document.service.LockService#lockTransaction(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, 537 * org.kuali.rice.kim.bo.Person) 538 */ 539 @Transactional 540 public BudgetConstructionLockStatus lockTransaction(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { 541 String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode(); 542 String accountNumber = appointmentFunding.getAccountNumber(); 543 String subAccountNumber = appointmentFunding.getSubAccountNumber(); 544 Integer fiscalYear = appointmentFunding.getUniversityFiscalYear(); 545 546 return this.lockTransaction(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, person.getPrincipalId()); 547 } 548 549 /** 550 * @see org.kuali.kfs.module.bc.document.service.LockService#isTransactionLocked(java.lang.String, java.lang.String, 551 * java.lang.String, java.lang.Integer) 552 */ 553 @Transactional 554 public boolean isTransactionLocked(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear) { 555 556 BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); 557 if (freshBcHeader != null) { 558 if (freshBcHeader.getBudgetTransactionLockUserIdentifier() != null) { 559 return true; 560 } 561 else { 562 return false; 563 } 564 } 565 else { 566 return false; // unlikely, but still means not locked 567 } 568 } 569 570 /** 571 * @see org.kuali.kfs.module.bc.document.service.LockService#isTransactionLockedByUser(java.lang.String, java.lang.String, 572 * java.lang.String, java.lang.Integer, java.lang.String) 573 */ 574 @Transactional 575 public boolean isTransactionLockedByUser(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear, String principalId) { 576 BudgetConstructionHeader freshBcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); 577 if (freshBcHeader != null && freshBcHeader.getBudgetTransactionLockUserIdentifier() != null && freshBcHeader.getBudgetTransactionLockUserIdentifier().equals(principalId)) { 578 return true; 579 } 580 581 return false; 582 } 583 584 /** 585 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockTransaction(java.lang.String, java.lang.String, 586 * java.lang.String, java.lang.Integer) 587 */ 588 @Transactional 589 public LockStatus unlockTransaction(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear) { 590 591 LockStatus lockStatus = LockStatus.NO_DOOR; 592 593 BudgetConstructionHeader bcHeader = budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); 594 if (bcHeader != null) { 595 if (bcHeader.getBudgetTransactionLockUserIdentifier() != null) { 596 bcHeader.setBudgetTransactionLockUserIdentifier(null); 597 try { 598 budgetConstructionDao.saveBudgetConstructionHeader(bcHeader); 599 lockStatus = LockStatus.SUCCESS; 600 } 601 catch (DataAccessException ex) { 602 lockStatus = LockStatus.OPTIMISTIC_EX; 603 } 604 } 605 else { 606 lockStatus = LockStatus.SUCCESS; // already unlocked 607 } 608 } 609 else { 610 lockStatus = LockStatus.NO_DOOR; // target not found 611 } 612 return lockStatus; 613 } 614 615 /** 616 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockTransaction(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding, 617 * org.kuali.rice.kim.bo.Person) 618 */ 619 @Transactional 620 public void unlockTransaction(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person person) { 621 String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode(); 622 String accountNumber = appointmentFunding.getAccountNumber(); 623 String subAccountNumber = appointmentFunding.getSubAccountNumber(); 624 Integer fiscalYear = appointmentFunding.getUniversityFiscalYear(); 625 String principalId = person.getPrincipalId(); 626 627 if (this.isTransactionLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId)) { 628 this.unlockTransaction(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear); 629 } 630 } 631 632 /** 633 * @see org.kuali.kfs.module.bc.document.service.LockService#getAllAccountLocks(String lockUserId) 634 */ 635 @Transactional 636 public List<BudgetConstructionHeader> getAllAccountLocks(String lockUserId) { 637 return budgetConstructionLockDao.getAllAccountLocks(lockUserId); 638 } 639 640 /** 641 * @see org.kuali.kfs.module.bc.document.service.LockService#getAllFundLocks(String lockUserId) 642 */ 643 @Transactional 644 public List<BudgetConstructionFundingLock> getOrphanedFundingLocks(String lockUserId) { 645 return budgetConstructionLockDao.getOrphanedFundingLocks(lockUserId); 646 } 647 648 /** 649 * @see org.kuali.kfs.module.bc.document.service.LockService#getOrphanedPositionLocks(String lockUserId) 650 */ 651 @Transactional 652 public List<BudgetConstructionPosition> getOrphanedPositionLocks(String lockUserId) { 653 return budgetConstructionLockDao.getOrphanedPositionLocks(lockUserId); 654 } 655 656 /** 657 * @see org.kuali.kfs.module.bc.document.service.LockService#getAllTransactionLocks(String lockUserId) 658 */ 659 @Transactional 660 public List<BudgetConstructionHeader> getAllTransactionLocks(String lockUserId) { 661 return budgetConstructionLockDao.getAllTransactionLocks(lockUserId); 662 } 663 664 /** 665 * @see org.kuali.kfs.module.bc.document.service.LockService#getAllPositionFundingLocks(java.lang.String) 666 */ 667 @Transactional 668 public List<PendingBudgetConstructionAppointmentFunding> getAllPositionFundingLocks(String lockUserId) { 669 return budgetConstructionLockDao.getAllPositionFundingLocks(lockUserId); 670 } 671 672 /** 673 * @see org.kuali.kfs.module.bc.document.service.LockService#checkLockExists(org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockSummary) 674 */ 675 @Transactional 676 public boolean checkLockExists(BudgetConstructionLockSummary lockSummary) { 677 String lockType = lockSummary.getLockType(); 678 679 if (BCConstants.LockTypes.ACCOUNT_LOCK.equals(lockType)) { 680 return this.isAccountLockedByUser(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 681 } 682 683 if (BCConstants.LockTypes.TRANSACTION_LOCK.equals(lockType)) { 684 return this.isTransactionLockedByUser(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 685 } 686 687 if (BCConstants.LockTypes.FUNDING_LOCK.equals(lockType)) { 688 return this.isFundingLockedByUser(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 689 } 690 691 if (BCConstants.LockTypes.POSITION_LOCK.equals(lockType)) { 692 return this.isPositionLockedByUser(lockSummary.getPositionNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 693 } 694 695 if (BCConstants.LockTypes.POSITION_FUNDING_LOCK.equals(lockType)) { 696 return this.isPositionFundingLockedByUser(lockSummary.getPositionNumber(), lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 697 } 698 699 return false; 700 } 701 702 /** 703 * @see org.kuali.kfs.module.bc.document.service.LockService#doUnlock(org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockSummary) 704 */ 705 @Transactional 706 public LockStatus doUnlock(BudgetConstructionLockSummary lockSummary) { 707 String lockType = lockSummary.getLockType(); 708 709 if (BCConstants.LockTypes.ACCOUNT_LOCK.equals(lockType)) { 710 BudgetConstructionHeader bcHeader = budgetConstructionDao.getByCandidateKey(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear()); 711 if (bcHeader != null) { 712 return this.unlockAccount(bcHeader); 713 } 714 } 715 716 if (BCConstants.LockTypes.TRANSACTION_LOCK.equals(lockType)) { 717 return this.unlockTransaction(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear()); 718 } 719 720 if (BCConstants.LockTypes.FUNDING_LOCK.equals(lockType)) { 721 return this.unlockFunding(lockSummary.getChartOfAccountsCode(), lockSummary.getAccountNumber(), lockSummary.getSubAccountNumber(), lockSummary.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 722 } 723 724 if (BCConstants.LockTypes.POSITION_LOCK.equals(lockType)) { 725 return this.unlockPosition(lockSummary.getPositionNumber(), lockSummary.getUniversityFiscalYear()); 726 } 727 728 if (BCConstants.LockTypes.POSITION_FUNDING_LOCK.equals(lockType)) { 729 BudgetConstructionPosition position = budgetConstructionDao.getByPrimaryId(lockSummary.getPositionNumber(), lockSummary.getUniversityFiscalYear()); 730 for (PendingBudgetConstructionAppointmentFunding appointmentFunding : position.getPendingBudgetConstructionAppointmentFunding()) { 731 this.unlockFunding(appointmentFunding.getChartOfAccountsCode(), appointmentFunding.getAccountNumber(), appointmentFunding.getSubAccountNumber(), appointmentFunding.getUniversityFiscalYear(), lockSummary.getLockUser().getPrincipalId()); 732 } 733 734 return this.unlockPosition(position.getPositionNumber(), position.getUniversityFiscalYear()); 735 } 736 737 return LockStatus.NO_DOOR; 738 } 739 740 /** 741 * @see org.kuali.kfs.module.bc.document.service.LockService#isAccountLockedByUser(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, 742 * org.kuali.rice.kim.bo.Person) 743 */ 744 @Transactional 745 public boolean isAccountLockedByUser(BudgetConstructionHeader budgetConstructionHeader, Person person) { 746 String chartOfAccountsCode = budgetConstructionHeader.getChartOfAccountsCode(); 747 String accountNumber = budgetConstructionHeader.getAccountNumber(); 748 String subAccountNumber = budgetConstructionHeader.getSubAccountNumber(); 749 Integer fiscalYear = budgetConstructionHeader.getUniversityFiscalYear(); 750 return this.isAccountLockedByUser(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, person.getPrincipalId()); 751 } 752 753 @NonTransactional 754 public void setBudgetConstructionDao(BudgetConstructionDao bcHeaderDao) { 755 this.budgetConstructionDao = bcHeaderDao; 756 } 757 758 /** 759 * Sets the budgetConstructionLockDao attribute value. 760 * 761 * @param budgetConstructionLockDao The budgetConstructionLockDao to set. 762 */ 763 @NonTransactional 764 public void setBudgetConstructionLockDao(BudgetConstructionLockDao budgetConstructionLockDao) { 765 this.budgetConstructionLockDao = budgetConstructionLockDao; 766 } 767 768 /** 769 * Sets the budgetDocumentService attribute value. 770 * 771 * @param budgetDocumentService The budgetDocumentService to set. 772 */ 773 @NonTransactional 774 public void setBudgetDocumentService(BudgetDocumentService budgetDocumentService) { 775 this.budgetDocumentService = budgetDocumentService; 776 } 777 778 /** 779 * @see org.kuali.kfs.module.bc.document.service.LockService#lockPendingBudgetConstructionAppointmentFundingRecords(java.util.List, 780 * org.kuali.rice.kim.bo.Person) 781 */ 782 @Transactional(propagation = Propagation.REQUIRES_NEW) 783 public List<PendingBudgetConstructionAppointmentFunding> lockPendingBudgetConstructionAppointmentFundingRecords(List<PendingBudgetConstructionAppointmentFunding> fundingRecords, Person user) throws BudgetConstructionLockUnavailableException { 784 List<PendingBudgetConstructionAppointmentFunding> lockedFundingRecords = new ArrayList<PendingBudgetConstructionAppointmentFunding>(); 785 Map<String, PendingBudgetConstructionAppointmentFunding> lockMap = new HashMap<String, PendingBudgetConstructionAppointmentFunding>(); 786 787 for (PendingBudgetConstructionAppointmentFunding fundingRecord : fundingRecords) { 788 BudgetConstructionHeader header = budgetDocumentService.getBudgetConstructionHeader(fundingRecord); 789 String lockingKey = fundingRecord.getUniversityFiscalYear() + "-" + fundingRecord.getChartOfAccountsCode() + "-" + fundingRecord.getAccountNumber() + "-" + fundingRecord.getSubAccountNumber(); 790 if (!lockMap.containsKey(lockingKey)) { 791 BudgetConstructionLockStatus lockStatus = lockAccount(header, user.getPrincipalId()); 792 if (lockStatus.getLockStatus().equals(BCConstants.LockStatus.BY_OTHER)) { 793 throw new BudgetConstructionLockUnavailableException(lockStatus); 794 } 795 else if (lockStatus.getLockStatus().equals(BCConstants.LockStatus.FLOCK_FOUND)) { 796 throw new BudgetConstructionLockUnavailableException(lockStatus); 797 } 798 else if (!lockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { 799 throw new BudgetConstructionLockUnavailableException(lockStatus); 800 } 801 else { 802 lockMap.put(lockingKey, fundingRecord); 803 lockedFundingRecords.add(fundingRecord); 804 } 805 } 806 } 807 808 return lockedFundingRecords; 809 } 810 811 /** 812 * @see org.kuali.kfs.module.bc.document.service.LockService#lockAccountAndCommit(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader, 813 * java.lang.String) 814 */ 815 @Transactional(propagation = Propagation.REQUIRES_NEW) 816 public BudgetConstructionLockStatus lockAccountAndCommit(BudgetConstructionHeader bcHeader, String principalId) { 817 return lockAccount(bcHeader, principalId); 818 } 819 820 /** 821 * @see org.kuali.kfs.module.bc.document.service.LockService#lockPositionAndActiveFunding(java.lang.Integer, java.lang.String, 822 * java.lang.String) 823 */ 824 @Transactional(propagation = Propagation.REQUIRES_NEW) 825 public BudgetConstructionLockStatus lockPositionAndActiveFunding(Integer universityFiscalYear, String positionNumber, String principalId) { 826 // attempt to lock position first 827 BudgetConstructionLockStatus lockStatus = lockPosition(positionNumber, universityFiscalYear, principalId); 828 if (!lockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { 829 return lockStatus; 830 } 831 832 // retrieve funding records for the position 833 List<PendingBudgetConstructionAppointmentFunding> allPositionFunding = budgetConstructionDao.getAllFundingForPosition(universityFiscalYear, positionNumber); 834 835 // lock funding if not marked as delete 836 for (PendingBudgetConstructionAppointmentFunding appointmentFunding : allPositionFunding) { 837 if (!appointmentFunding.isAppointmentFundingDeleteIndicator()) { 838 BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding); 839 BudgetConstructionLockStatus fundingLockStatus = lockFunding(budgetConstructionHeader, principalId); 840 841 if (!fundingLockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { 842 return lockStatus; 843 } 844 } 845 } 846 847 // successfully obtained all locks 848 BudgetConstructionLockStatus bcLockStatus = new BudgetConstructionLockStatus(); 849 bcLockStatus.setLockStatus(LockStatus.SUCCESS); 850 851 return bcLockStatus; 852 } 853 854 /** 855 * @see org.kuali.kfs.module.bc.document.service.LockService#unlockPositionAndActiveFunding(java.lang.Integer, java.lang.String, 856 * java.lang.String) 857 */ 858 @Transactional(propagation = Propagation.REQUIRES_NEW) 859 public LockStatus unlockPositionAndActiveFunding(Integer universityFiscalYear, String positionNumber, String principalId) { 860 // unlock position 861 LockStatus lockStatus = unlockPosition(positionNumber, universityFiscalYear, principalId); 862 if (!lockStatus.equals(BCConstants.LockStatus.SUCCESS)) { 863 return lockStatus; 864 } 865 866 // retrieve funding records for the position 867 List<PendingBudgetConstructionAppointmentFunding> allPositionFunding = budgetConstructionDao.getAllFundingForPosition(universityFiscalYear, positionNumber); 868 869 // unlock funding if not marked as delete 870 for (PendingBudgetConstructionAppointmentFunding appointmentFunding : allPositionFunding) { 871 if (!appointmentFunding.isAppointmentFundingDeleteIndicator()) { 872 LockStatus fundingLockStatus = unlockFunding(appointmentFunding.getChartOfAccountsCode(), appointmentFunding.getAccountNumber(), appointmentFunding.getSubAccountNumber(), appointmentFunding.getUniversityFiscalYear(), principalId); 873 874 if (!fundingLockStatus.equals(BCConstants.LockStatus.SUCCESS)) { 875 return lockStatus; 876 } 877 } 878 } 879 880 // successfully completed unlocks 881 return LockStatus.SUCCESS; 882 } 883 884 }