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    }