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 }