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.coa.document;
017    
018    import java.util.ArrayList;
019    import java.util.HashMap;
020    import java.util.List;
021    import java.util.Map;
022    
023    import org.kuali.kfs.coa.businessobject.ObjectCode;
024    import org.kuali.kfs.coa.businessobject.ObjectCodeGlobal;
025    import org.kuali.kfs.coa.businessobject.ObjectCodeGlobalDetail;
026    import org.kuali.kfs.coa.service.ObjectCodeService;
027    import org.kuali.kfs.coa.service.SubObjectTrickleDownInactivationService;
028    import org.kuali.kfs.sys.KFSConstants;
029    import org.kuali.kfs.sys.context.SpringContext;
030    import org.kuali.kfs.sys.document.FinancialSystemGlobalMaintainable;
031    import org.kuali.rice.kns.bo.GlobalBusinessObject;
032    import org.kuali.rice.kns.bo.PersistableBusinessObject;
033    import org.kuali.rice.kns.document.MaintenanceDocument;
034    import org.kuali.rice.kns.document.MaintenanceLock;
035    import org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl;
036    import org.kuali.rice.kns.service.BusinessObjectService;
037    import org.kuali.rice.kns.util.KNSConstants;
038    import org.kuali.rice.kns.util.ObjectUtils;
039    
040    /**
041     * This class provides some specific functionality for the {@link ObjectCodeGlobal} maintenance document refresh - sets the current
042     * fiscal year from the {@link ObjectCodeGlobalDetail} prepareGlobalsForSave - sets the object code on each detail object in the
043     * collection generateMaintenanceLocks - generates the appropriate maintenance locks for the {@link ObjectCode}
044     */
045    public class ObjectCodeGlobalMaintainableImpl extends FinancialSystemGlobalMaintainable {
046        private static String CHANGE_DETAIL_COLLECTION = "objectCodeGlobalDetails";
047    
048        //@Override
049        //public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
050        //  if our detail objects have a null fiscal year we need to fill these in with the "addLine" fiscal year
051        //      otherwise we leave it alone, these should only be null when coming back from a multiple value lookup
052        //    if (refreshCaller != null && refreshCaller.equals(KFSConstants.MULTIPLE_VALUE)) {
053        //        ObjectCodeGlobal objectCodeGlobal = (ObjectCodeGlobal) document.getDocumentBusinessObject();
054        //        ObjectCodeGlobalDetail addLineDetail = (ObjectCodeGlobalDetail) newCollectionLines.get(CHANGE_DETAIL_COLLECTION);
055        //        int fiscalYear = addLineDetail.getUniversityFiscalYear();
056        //        for (ObjectCodeGlobalDetail detail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
057        //            if (detail.getUniversityFiscalYear() == null) {
058        //                detail.setUniversityFiscalYear(fiscalYear);
059        //            }
060        //        }
061        //    }
062    
063        //    super.refresh(refreshCaller, fieldValues, document);
064        //}
065    
066        //
067        // @Override
068        // protected List<String> getMultiValueIdentifierList(Collection maintCollection) {
069        // List<String> identifierList = new ArrayList<String>();
070        // for (ObjectCodeGlobalDetail bo : (Collection<ObjectCodeGlobalDetail>)maintCollection) {
071        // identifierList.add(bo.getChartOfAccountsCode());
072        // }
073        // return identifierList;
074        // }
075    
076        // @Override
077        // protected boolean hasBusinessObjectExistedInLookupResult(BusinessObject bo, List<String> existingIdentifierList) {
078        // // default implementation does nothing
079        // if (existingIdentifierList.contains(((ObjectCodeGlobalDetail)bo).getChartOfAccountsCode())) {
080        // return true;
081        // }
082        // else {
083        // return false;
084        // }
085        // }
086    
087        /**
088         * This method sets the object code on each detail object in the collection
089         */
090        @Override
091        protected void prepareGlobalsForSave() {
092            // copy the object code down from the header into the details
093            ObjectCodeGlobal objectCodeGlobal = (ObjectCodeGlobal) getBusinessObject();
094    
095            for (ObjectCodeGlobalDetail detail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
096                detail.setFinancialObjectCode(objectCodeGlobal.getFinancialObjectCode());
097            }
098            super.prepareGlobalsForSave();
099        }
100    
101        /**
102         * This generates the appropriate maintenance locks for the {@link ObjectCode}
103         * 
104         * @see org.kuali.rice.kns.maintenance.Maintainable#generateMaintenanceLocks()
105         */
106        @Override
107        public List<MaintenanceLock> generateMaintenanceLocks() {
108            ObjectCodeGlobal objectCodeGlobal = (ObjectCodeGlobal) getBusinessObject();
109            List<MaintenanceLock> maintenanceLocks = new ArrayList();
110            SubObjectTrickleDownInactivationService subObjectTrickleDownInactivationService = SpringContext.getBean(SubObjectTrickleDownInactivationService.class);
111            
112            for (ObjectCodeGlobalDetail detail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
113                MaintenanceLock maintenanceLock = new MaintenanceLock();
114                StringBuffer lockrep = new StringBuffer();
115    
116                lockrep.append(ObjectCode.class.getName() + KFSConstants.Maintenance.AFTER_CLASS_DELIM);
117                lockrep.append("universityFiscalYear" + KFSConstants.Maintenance.AFTER_FIELDNAME_DELIM);
118                lockrep.append(detail.getUniversityFiscalYear() + KFSConstants.Maintenance.AFTER_VALUE_DELIM);
119                lockrep.append("chartOfAccountsCode" + KFSConstants.Maintenance.AFTER_FIELDNAME_DELIM);
120                lockrep.append(detail.getChartOfAccountsCode() + KFSConstants.Maintenance.AFTER_VALUE_DELIM);
121                lockrep.append("financialObjectCode" + KFSConstants.Maintenance.AFTER_FIELDNAME_DELIM);
122                lockrep.append(detail.getFinancialObjectCode());
123    
124                maintenanceLock.setDocumentNumber(objectCodeGlobal.getDocumentNumber());
125                maintenanceLock.setLockingRepresentation(lockrep.toString());
126                maintenanceLocks.add(maintenanceLock);
127                
128                ObjectCode objectCode = new ObjectCode();
129                objectCode.setUniversityFiscalYear(detail.getUniversityFiscalYear());
130                objectCode.setChartOfAccountsCode(detail.getChartOfAccountsCode());
131                objectCode.setFinancialObjectCode(detail.getFinancialObjectCode());
132                objectCode.setActive(objectCodeGlobal.isFinancialObjectActiveIndicator());
133                
134                if (isInactivatingObjectCode(objectCode)) {
135                    // if it turns out that the object code does not have associated sub-objects (either because the object code doesn't exist or doesn't have sub-objects)
136                    // then the generateTrickleDownMaintenanceLocks method returns an empty list 
137                    maintenanceLocks.addAll(subObjectTrickleDownInactivationService.generateTrickleDownMaintenanceLocks(objectCode, documentNumber));
138                }
139            }
140            return maintenanceLocks;
141        }
142        
143        /**
144         * @see org.kuali.rice.kns.maintenance.Maintainable#saveBusinessObject()
145         */
146        @Override
147        public void saveBusinessObject() {
148            BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);
149            
150            GlobalBusinessObject gbo = (GlobalBusinessObject) businessObject;
151    
152            // delete any indicated BOs
153            List<PersistableBusinessObject> bosToDeactivate = gbo.generateDeactivationsToPersist();
154            if (bosToDeactivate != null) {
155                if (!bosToDeactivate.isEmpty()) {
156                    boService.save(bosToDeactivate);
157                }
158            }
159            
160            // OJB caches the any ObjectCodes that are retrieved from the database.  If multiple queries return the same row (identified by the PK
161            // values), OJB will return the same instance of the ObjectCode.  However, in generateGlobalChangesToPersist(), the ObjectCode returned by
162            // OJB is altered, meaning that any subsequent OJB calls will return the altered object.  The following cache will store the active statuses
163            // of object codes affected by this global document before generateGlobalChangesToPersist() alters them.
164            Map<String, Boolean> objectCodeActiveStatusCache = buildObjectCodeActiveStatusCache((ObjectCodeGlobal) gbo);
165            
166            SubObjectTrickleDownInactivationService subObjectTrickleDownInactivationService = SpringContext.getBean(SubObjectTrickleDownInactivationService.class);
167            // persist any indicated BOs
168            List<PersistableBusinessObject> bosToPersist = gbo.generateGlobalChangesToPersist();
169            if (bosToPersist != null) {
170                if (!bosToPersist.isEmpty()) {
171                    for (PersistableBusinessObject bo : bosToPersist) {
172                        ObjectCode objectCode = (ObjectCode) bo;
173                        
174                        boService.save(objectCode);
175                        
176                        if (isInactivatingObjectCode(objectCode, objectCodeActiveStatusCache)) {
177                            subObjectTrickleDownInactivationService.trickleDownInactivateSubObjects(objectCode, documentNumber);
178                        }
179                    }
180                }
181            }
182        }
183        
184        protected boolean isInactivatingObjectCode(ObjectCode objectCode) {
185            ObjectCodeService objectCodeService = SpringContext.getBean(ObjectCodeService.class);
186            if (!objectCode.isActive()) {
187                ObjectCode objectCodeFromDB = objectCodeService.getByPrimaryId(objectCode.getUniversityFiscalYear(), objectCode.getChartOfAccountsCode(), objectCode.getFinancialObjectCode());
188                if (objectCodeFromDB != null && objectCodeFromDB.isActive()) {
189                    return true;
190                }
191            }
192            return false;
193        }
194        
195        protected boolean isInactivatingObjectCode(ObjectCode objectCode, Map<String, Boolean> objectCodeActiveStatusCache) {
196            if (!objectCode.isActive()) {
197                if (Boolean.TRUE.equals(objectCodeActiveStatusCache.get(buildObjectCodeCachingKey(objectCode)))) {
198                    return true;
199                }
200            }
201            return false;
202        }
203        
204        protected String buildObjectCodeCachingKey(ObjectCode objectCode) {
205            return objectCode.getUniversityFiscalYear() + KNSConstants.Maintenance.AFTER_VALUE_DELIM + objectCode.getChartOfAccountsCode() + 
206                    KNSConstants.Maintenance.AFTER_VALUE_DELIM + objectCode.getFinancialObjectCode(); 
207        }
208        
209        protected Map<String, Boolean> buildObjectCodeActiveStatusCache(ObjectCodeGlobal objectCodeGlobal) {
210            ObjectCodeService objectCodeService = SpringContext.getBean(ObjectCodeService.class);
211            Map<String, Boolean> cache = new HashMap<String, Boolean>();
212            for (ObjectCodeGlobalDetail detail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
213                ObjectCode objectCodeFromDB = objectCodeService.getByPrimaryId(detail.getUniversityFiscalYear(), detail.getChartOfAccountsCode(), objectCodeGlobal.getFinancialObjectCode());
214                if (ObjectUtils.isNotNull(objectCodeFromDB)) {
215                    cache.put(buildObjectCodeCachingKey(objectCodeFromDB), Boolean.valueOf(objectCodeFromDB.isActive()));
216                }
217            }
218            return cache;
219        }
220    
221        @Override
222        public Class<? extends PersistableBusinessObject> getPrimaryEditedBusinessObjectClass() {
223            return ObjectCode.class;
224        }
225    }