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.service.impl;
017
018 import java.util.Collections;
019 import java.util.HashMap;
020 import java.util.HashSet;
021 import java.util.Iterator;
022 import java.util.List;
023 import java.util.Map;
024 import java.util.Set;
025
026 import org.apache.commons.lang.StringUtils;
027 import org.kuali.kfs.coa.businessobject.Account;
028 import org.kuali.kfs.coa.service.AccountPersistenceStructureService;
029 import org.kuali.kfs.sys.KFSPropertyConstants;
030 import org.kuali.rice.kns.bo.PersistableBusinessObject;
031 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
032 import org.kuali.rice.kns.service.impl.PersistenceStructureServiceImpl;
033 import org.kuali.rice.kns.util.KNSConstants;
034
035 public class AccountPersistenceStructureServiceImpl extends PersistenceStructureServiceImpl implements AccountPersistenceStructureService {
036
037 /*
038 * The following list is commented out as it's not used in code anymore, but still can server as a reference for testing.
039 * The list causes problems when referencing AwardAccount.class, a class in optional module;
040 * also it's not a good practice to hard-code the list as it may have to be expanded when new sub/classes are added.
041 * Instead of using "if (ACCOUNT_CLASSES.contains(clazz))" to judge whether whether a class is account-related,
042 * we now judge by whether the PKs of the class contain chartOfAccountsCode-accountNumber.
043 *
044 *
045 // List of account-related BO classes (and all their subclasses) which have chartOfAccountsCode and accountNumber as (part of) the primary keys,
046 // i.e. the complete list of all possible referenced BO classes with chart code and account number as (part of) the foreign keys.
047 protected static final HashSet<Class<? extends BusinessObject>> ACCOUNT_CLASSES = new HashSet<Class<? extends BusinessObject>>();
048 static {
049 ACCOUNT_CLASSES.add(Account.class);
050 ACCOUNT_CLASSES.add(SubAccount.class);
051 ACCOUNT_CLASSES.add(A21SubAccount.class);
052 ACCOUNT_CLASSES.add(AwardAccount.class); // this class can't be referenced by core code
053 ACCOUNT_CLASSES.add(IndirectCostRecoveryExclusionAccount.class);
054 ACCOUNT_CLASSES.add(PriorYearAccount.class);
055 ACCOUNT_CLASSES.add(AccountDelegate.class);
056 ACCOUNT_CLASSES.add(AccountDescription.class);
057 ACCOUNT_CLASSES.add(AccountGlobalDetail.class);
058 ACCOUNT_CLASSES.add(AccountGuideline.class);
059 ACCOUNT_CLASSES.add(SubObjectCode.class);
060 ACCOUNT_CLASSES.add(SubObjectCodeCurrent.class);
061 }
062 */
063
064 public boolean isAccountRelatedClass(Class clazz) {
065 List<String> pks = listPrimaryKeyFieldNames(clazz);
066
067 if (pks.contains(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE) && pks.contains(KFSPropertyConstants.ACCOUNT_NUMBER )) {
068 return true;
069 }
070 else {
071 return false;
072 }
073 }
074
075 private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
076
077 public void setMaintenanceDocumentDictionaryService(MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService) {
078 this.maintenanceDocumentDictionaryService = maintenanceDocumentDictionaryService;
079 }
080
081 @SuppressWarnings("rawtypes")
082 public Map<String, Class> listCollectionAccountFields(PersistableBusinessObject bo) {
083 Map<String, Class> accountFields = new HashMap<String, Class>();
084 Iterator<Map.Entry<String, Class>> collObjs = listCollectionObjectTypes(bo).entrySet().iterator();
085
086 while (collObjs.hasNext()) {
087 Map.Entry<String, Class> entry = (Map.Entry<String, Class>)collObjs.next();
088 String accountCollName = entry.getKey();
089 Class accountCollType = entry.getValue();
090
091 // if the reference object is of Account or Account-involved BO class (including all subclasses)
092 if (isAccountRelatedClass(accountCollType)) {
093 // exclude non-maintainable account collection
094 String docTypeName = maintenanceDocumentDictionaryService.getDocumentTypeName(bo.getClass());
095 if (maintenanceDocumentDictionaryService.getMaintainableCollection(docTypeName, accountCollName) == null)
096 continue;
097
098 // otherwise include the account field
099 accountFields.put(accountCollName, accountCollType);
100 }
101 }
102
103 return accountFields;
104 }
105
106 @SuppressWarnings("rawtypes")
107 public Set<String> listCollectionChartOfAccountsCodeNames(PersistableBusinessObject bo) {
108 Set<String> coaCodeNames = new HashSet<String>();
109 String docTypeName = maintenanceDocumentDictionaryService.getDocumentTypeName(bo.getClass());
110 Iterator<Map.Entry<String, Class>> collObjs = listCollectionObjectTypes(bo).entrySet().iterator();
111
112 while (collObjs.hasNext()) {
113 Map.Entry<String, Class> entry = (Map.Entry<String, Class>)collObjs.next();
114 String accountCollName = entry.getKey();
115 Class accountCollType = entry.getValue();
116
117 // if the reference object is of Account or Account-involved BO class (including all subclasses)
118 if (isAccountRelatedClass(accountCollType)) {
119 // exclude non-maintainable account collection
120 if (maintenanceDocumentDictionaryService.getMaintainableCollection(docTypeName, accountCollName) == null)
121 continue;
122
123 // otherwise include the account field
124 String coaCodeName = KNSConstants.ADD_PREFIX + "." + accountCollName + "." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE;
125 coaCodeNames.add(coaCodeName);
126 }
127 }
128
129 return coaCodeNames;
130 }
131
132 @SuppressWarnings("rawtypes")
133 public Map<String, Class> listReferenceAccountFields(PersistableBusinessObject bo) {
134 Map<String, Class> accountFields = new HashMap<String, Class>();
135 Iterator<Map.Entry<String, Class>> refObjs = listReferenceObjectFields(bo).entrySet().iterator();
136
137 while (refObjs.hasNext()) {
138 Map.Entry<String, Class> entry = (Map.Entry<String, Class>)refObjs.next();
139 String accountName = entry.getKey();
140 Class accountType = entry.getValue();
141
142 // if the reference object is of Account or Account-involved BO class (including all subclasses)
143 if (isAccountRelatedClass(accountType)) {
144 String coaCodeName = getForeignKeyFieldName(bo.getClass(), accountName, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
145 String acctNumName = getForeignKeyFieldName(bo.getClass(), accountName, KFSPropertyConstants.ACCOUNT_NUMBER);
146
147 // exclude the case when chartOfAccountsCode-accountNumber don't exist as foreign keys in the BO:
148 // for ex, in SubAccount, a21SubAccount is a reference object but its PKs don't exist as FKs in SubAccount;
149 // rather, A21SubAccount has a nested reference account - costShareAccount,
150 // whose PKs exists in A21SubAccount as FKs, and are used in SubAccount maint doc as nested reference;
151 // special treatment outside this method is needed for this case
152 if (StringUtils.isEmpty(coaCodeName) || StringUtils.isEmpty(acctNumName))
153 continue;
154
155 // in general we do want to have chartOfAccountsCode fields readOnly/auto-populated even when they are part of PKs,
156 // (such as in SubAccount), as the associated account shall only be chosen from existing accounts;
157 // however, when the BO is Account itself, we don't want to make the PK chartOfAccountsCode field readOnly,
158 // as it shall be editable when a new Account is being created; so we shall exclude such case
159 List<String> pks = listPrimaryKeyFieldNames(bo.getClass());
160 if (bo instanceof Account && pks.contains(coaCodeName) && pks.contains(acctNumName ))
161 continue;
162
163 // exclude non-maintainable account field
164 String docTypeName = maintenanceDocumentDictionaryService.getDocumentTypeName(bo.getClass());
165 if (maintenanceDocumentDictionaryService.getMaintainableField(docTypeName, coaCodeName) == null ||
166 maintenanceDocumentDictionaryService.getMaintainableField(docTypeName, acctNumName) == null)
167 continue;
168
169 // otherwise include the account field
170 accountFields.put(accountName, accountType);
171 }
172 }
173
174 return accountFields;
175 }
176
177 @SuppressWarnings("rawtypes")
178 public Map<String, String> listChartCodeAccountNumberPairs(PersistableBusinessObject bo) {
179 Map<String, String> chartAccountPairs = new HashMap<String, String>();
180 Iterator<Map.Entry<String, Class>> refObjs = listReferenceObjectFields(bo).entrySet().iterator();
181
182 while (refObjs.hasNext()) {
183 Map.Entry<String, Class> entry = (Map.Entry<String, Class>)refObjs.next();
184 String accountName = entry.getKey();
185 Class accountType = entry.getValue();
186
187 // if the reference object is of Account or Account-involved BO class (including all subclasses)
188 if (isAccountRelatedClass(accountType)) {
189 String coaCodeName = getForeignKeyFieldName(bo.getClass(), accountName, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
190 String acctNumName = getForeignKeyFieldName(bo.getClass(), accountName, KFSPropertyConstants.ACCOUNT_NUMBER);
191
192 // exclude the case when chartOfAccountsCode-accountNumber don't exist as foreign keys in the BO:
193 // for ex, in SubAccount, a21SubAccount is a reference object but its PKs don't exist as FKs in SubAccount;
194 // rather, A21SubAccount has a nested reference account - costShareAccount,
195 // whose PKs exists in A21SubAccount as FKs, and are used in SubAccount maint doc as nested reference
196 // special treatment outside this method is needed for this case
197 if (StringUtils.isEmpty(coaCodeName) || StringUtils.isEmpty(acctNumName))
198 continue;
199
200 // in general we do want to have chartOfAccountsCode fields readOnly/auto-populated even when they are part of PKs,
201 // (such as in SubAccount), as the associated account shall only be chosen from existing accounts;
202 // however, when the BO is Account itself, we don't want to make the PK chartOfAccountsCode field readOnly,
203 // as it shall be editable when a new Account is being created; so we shall exclude such case
204 List<String> pks = listPrimaryKeyFieldNames(bo.getClass());
205 if (bo instanceof Account && pks.contains(coaCodeName) && pks.contains(acctNumName ))
206 continue;
207
208 // exclude non-maintainable account field
209 String docTypeName = maintenanceDocumentDictionaryService.getDocumentTypeName(bo.getClass());
210 if (maintenanceDocumentDictionaryService.getMaintainableField(docTypeName, coaCodeName) == null ||
211 maintenanceDocumentDictionaryService.getMaintainableField(docTypeName, acctNumName) == null)
212 continue;
213
214 // otherwise include the account field PKs
215 chartAccountPairs.put(coaCodeName, acctNumName);
216 }
217 }
218
219 return chartAccountPairs;
220 }
221
222 @SuppressWarnings("rawtypes")
223 public Map<String, String> listAccountNumberChartCodePairs(PersistableBusinessObject bo) {
224 Map<String, String> accountChartPairs = new HashMap<String, String>();
225 Iterator<Map.Entry<String, Class>> refObjs = listReferenceObjectFields(bo).entrySet().iterator();
226
227 while (refObjs.hasNext()) {
228 Map.Entry<String, Class> entry = (Map.Entry<String, Class>)refObjs.next();
229 String accountName = entry.getKey();
230 Class accountType = entry.getValue();
231
232 // if the reference object is of Account or Account-involved BO class (including all subclasses)
233 if (isAccountRelatedClass(accountType)) {
234 String coaCodeName = getForeignKeyFieldName(bo.getClass(), accountName, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
235 String acctNumName = getForeignKeyFieldName(bo.getClass(), accountName, KFSPropertyConstants.ACCOUNT_NUMBER);
236
237 // exclude the case when chartOfAccountsCode-accountNumber don't exist as foreign keys in the BO:
238 // for ex, in SubAccount, a21SubAccount is a reference object but its PKs don't exist as FKs in SubAccount;
239 // rather, A21SubAccount has a nested reference account - costShareAccount,
240 // whose PKs exists in A21SubAccount as FKs, and are used in SubAccount maint doc as nested reference
241 // special treatment outside this method is needed for this case
242 if (StringUtils.isEmpty(coaCodeName) || StringUtils.isEmpty(acctNumName))
243 continue;
244
245 // in general we do want to have chartOfAccountsCode fields readOnly/auto-populated even when they are part of PKs,
246 // (such as in SubAccount), as the associated account shall only be chosen from existing accounts;
247 // however, when the BO is Account itself, we don't want to make the PK chartOfAccountsCode field readOnly,
248 // as it shall be editable when a new Account is being created; so we shall exclude such case
249 List<String> pks = listPrimaryKeyFieldNames(bo.getClass());
250 if (bo instanceof Account && pks.contains(coaCodeName) && pks.contains(acctNumName ))
251 continue;
252
253 // exclude non-maintainable account field
254 String docTypeName = maintenanceDocumentDictionaryService.getDocumentTypeName(bo.getClass());
255 if (maintenanceDocumentDictionaryService.getMaintainableField(docTypeName, coaCodeName) == null ||
256 maintenanceDocumentDictionaryService.getMaintainableField(docTypeName, acctNumName) == null)
257 continue;
258
259 // otherwise include the account field PKs
260 accountChartPairs.put(acctNumName, coaCodeName);
261 }
262 }
263
264 return accountChartPairs;
265 }
266
267 public Set<String> listChartOfAccountsCodeNames(PersistableBusinessObject bo) {;
268 return listChartCodeAccountNumberPairs(bo).keySet();
269 }
270
271 public Set<String> listAccountNumberNames(PersistableBusinessObject bo) {
272 return listAccountNumberChartCodePairs(bo).keySet();
273 }
274
275 public String getChartOfAccountsCodeName(PersistableBusinessObject bo, String accountNumberName) {
276 return listAccountNumberChartCodePairs(bo).get(accountNumberName);
277 }
278
279 public String getAccountNumberName(PersistableBusinessObject bo, String chartOfAccountsCodeName) {
280 return listChartCodeAccountNumberPairs(bo).get(chartOfAccountsCodeName);
281 }
282
283 /**
284 * Need to stop this method from running for objects which are not bound into the ORM layer (OJB),
285 * for ex. OrgReviewRole is not persistable. In this case, we can just return an empty list.
286 *
287 * @see org.kuali.rice.kns.service.impl.PersistenceStructureServiceImpl#listReferenceObjectFields(org.kuali.rice.kns.bo.PersistableBusinessObject)
288 */
289 @SuppressWarnings("rawtypes")
290 @Override
291 public Map<String, Class> listReferenceObjectFields(PersistableBusinessObject bo) {
292 if ( isPersistable(bo.getClass() ) ) {
293 return super.listReferenceObjectFields(bo);
294 }
295 return Collections.emptyMap();
296 }
297
298 }