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 }