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 }