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.sys.batch.service.impl; 017 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.HashMap; 021 import java.util.HashSet; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.Set; 025 026 import org.apache.commons.collections.CollectionUtils; 027 import org.apache.commons.lang.StringUtils; 028 import org.apache.log4j.Logger; 029 import org.kuali.kfs.sys.FinancialSystemModuleConfiguration; 030 import org.kuali.kfs.sys.KFSConstants; 031 import org.kuali.kfs.sys.batch.FiscalYearMakerStep; 032 import org.kuali.kfs.sys.batch.dataaccess.FiscalYearMaker; 033 import org.kuali.kfs.sys.batch.dataaccess.FiscalYearMakersDao; 034 import org.kuali.kfs.sys.batch.service.FiscalYearMakerService; 035 import org.kuali.rice.kns.bo.PersistableBusinessObject; 036 import org.kuali.rice.kns.service.KualiModuleService; 037 import org.kuali.rice.kns.service.ModuleService; 038 import org.kuali.rice.kns.service.ParameterService; 039 import org.springframework.transaction.annotation.Transactional; 040 041 /** 042 * @see org.kuali.kfs.coa.batch.service.FiscalYearMakerService 043 */ 044 @Transactional 045 public class FiscalYearMakerServiceImpl implements FiscalYearMakerService { 046 private static Logger LOG = org.apache.log4j.Logger.getLogger(FiscalYearMakerServiceImpl.class); 047 048 private FiscalYearMakersDao fiscalYearMakersDao; 049 private ParameterService parameterService; 050 private KualiModuleService kualiModuleService; 051 052 private List<FiscalYearMaker> fiscalYearMakers; 053 054 /** 055 * @see org.kuali.kfs.coa.batch.service.FiscalYearMakerService#runProcess() 056 */ 057 public void runProcess() { 058 String parmBaseYear = parameterService.getParameterValue(FiscalYearMakerStep.class, KFSConstants.ChartApcParms.FISCAL_YEAR_MAKER_SOURCE_FISCAL_YEAR); 059 if (StringUtils.isBlank(parmBaseYear)) { 060 throw new RuntimeException("Required fiscal year parameter " + KFSConstants.ChartApcParms.FISCAL_YEAR_MAKER_SOURCE_FISCAL_YEAR + " has not been set."); 061 } 062 063 Integer baseYear = Integer.parseInt(parmBaseYear); 064 boolean replaceMode = parameterService.getIndicatorParameter(FiscalYearMakerStep.class, KFSConstants.ChartApcParms.FISCAL_YEAR_MAKER_REPLACE_MODE); 065 066 if (fiscalYearMakers == null || fiscalYearMakers.isEmpty()) { 067 this.initialize(); 068 } 069 070 validateFiscalYearMakerConfiguration(); 071 072 // get correct order to do copy 073 List<FiscalYearMaker> copyList = getFiscalYearMakerHelpersInCopyOrder(); 074 075 // if configured to replace existing records first clear out any records in target year 076 if (replaceMode) { 077 List<FiscalYearMaker> deleteList = getFiscalYearMakerHelpersInDeleteOrder(copyList); 078 for (FiscalYearMaker fiscalYearMakerHelper : deleteList) { 079 if (fiscalYearMakerHelper.isAllowOverrideTargetYear()) { 080 fiscalYearMakersDao.deleteNewYearRows(baseYear, fiscalYearMakerHelper); 081 } 082 } 083 } 084 085 // Map to hold parent primary key values written to use for child RI checks 086 Map<Class<? extends PersistableBusinessObject>, Set<String>> parentKeysWritten = new HashMap<Class<? extends PersistableBusinessObject>, Set<String>>(); 087 088 // do copy process on each setup business object 089 for (FiscalYearMaker fiscalYearMaker : copyList) { 090 boolean isParent = isParentClass(fiscalYearMaker.getBusinessObjectClass()); 091 if (!fiscalYearMaker.doCustomProcessingOnly()) { 092 Collection<String> copyErrors = fiscalYearMakersDao.createNewYearRows(baseYear, fiscalYearMaker, replaceMode, parentKeysWritten, isParent); 093 writeCopyFailureMessages(copyErrors); 094 } 095 096 fiscalYearMaker.performCustomProcessing(baseYear, true); 097 098 // if copy two years call copy procedure again to copy records from base year + 1 to base year + 2 099 if (fiscalYearMaker.isTwoYearCopy()) { 100 if (!fiscalYearMaker.doCustomProcessingOnly()) { 101 Collection<String> copyErrors = fiscalYearMakersDao.createNewYearRows(baseYear + 1, fiscalYearMaker, replaceMode, parentKeysWritten, isParent); 102 writeCopyFailureMessages(copyErrors); 103 } 104 105 fiscalYearMaker.performCustomProcessing(baseYear + 1, false); 106 } 107 } 108 } 109 110 /** 111 * Returns List of <code>FiscalYearMaker</code> objects in the order they should be copied. Ordered by Parent classes first then 112 * children. This is necessary to ensure referential integrity is satisfied when the new record is inserted. 113 * 114 * @return List<FiscalYearMaker> in copy order 115 */ 116 protected List<FiscalYearMaker> getFiscalYearMakerHelpersInCopyOrder() { 117 List<Class<? extends PersistableBusinessObject>> classCopyOrder = new ArrayList<Class<? extends PersistableBusinessObject>>(); 118 119 // build map of parents and their children 120 Map<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>> parentChildren = getParentChildrenMap(); 121 122 // figure out correct order among parents by picking off levels of hierarchy 123 while (!parentChildren.isEmpty()) { 124 Set<Class<? extends PersistableBusinessObject>> parents = parentChildren.keySet(); 125 Set<Class<? extends PersistableBusinessObject>> children = getChildren(parentChildren); 126 127 Set<Class<? extends PersistableBusinessObject>> rootParents = new HashSet<Class<? extends PersistableBusinessObject>>(CollectionUtils.subtract(parents, children)); 128 129 // if there are no root parents, then we must have a circular reference 130 if (rootParents.isEmpty()) { 131 findCircularReferenceAndThrowException(parentChildren); 132 } 133 134 for (Class<? extends PersistableBusinessObject> rootParent : rootParents) { 135 classCopyOrder.add(rootParent); 136 parentChildren.remove(rootParent); 137 } 138 } 139 140 // now add remaining objects (those that are not parents) 141 for (FiscalYearMaker fiscalYearMakerHelper : this.fiscalYearMakers) { 142 if (!classCopyOrder.contains(fiscalYearMakerHelper.getBusinessObjectClass())) { 143 classCopyOrder.add(fiscalYearMakerHelper.getBusinessObjectClass()); 144 } 145 } 146 147 // finally build list of FiscalYearMaker objects by the correct class order 148 List<FiscalYearMaker> fiscalYearMakerHelpersCopyOrder = new ArrayList<FiscalYearMaker>(); 149 150 Map<Class<? extends PersistableBusinessObject>, FiscalYearMaker> copyMap = getFiscalYearMakerMap(); 151 for (Class<? extends PersistableBusinessObject> copyClass : classCopyOrder) { 152 fiscalYearMakerHelpersCopyOrder.add(copyMap.get(copyClass)); 153 } 154 155 return fiscalYearMakerHelpersCopyOrder; 156 } 157 158 /** 159 * Returns List of <code>FiscalYearMaker</code> objects in the order they should be deleted. Ordered by Child classes first then 160 * Parents. This is necessary to ensure referential integrity is satisfied when the new record is deleted. 161 * 162 * @param fiscalYearMakerHelpersCopyOrder list of fiscal year makers in copy order 163 * @return List<FiscalYearMaker> in delete order 164 */ 165 protected List<FiscalYearMaker> getFiscalYearMakerHelpersInDeleteOrder(List<FiscalYearMaker> fiscalYearMakerHelpersCopyOrder) { 166 List<FiscalYearMaker> fiscalYearMakerHelpersDeleteOrder = new ArrayList<FiscalYearMaker>(); 167 for (int i = fiscalYearMakerHelpersCopyOrder.size() - 1; i >= 0; i--) { 168 fiscalYearMakerHelpersDeleteOrder.add(fiscalYearMakerHelpersCopyOrder.get(i)); 169 } 170 171 return fiscalYearMakerHelpersDeleteOrder; 172 } 173 174 /** 175 * Finds circular references (class which is a child to itself) and throws exception indicating the invalid parent-child 176 * configuration 177 * 178 * @param parents Set of parent classes to check 179 * @param parentChildren Map with parent class as the key and its children classes as value 180 */ 181 protected void findCircularReferenceAndThrowException(Map<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>> parentChildren) { 182 Set<Class<? extends PersistableBusinessObject>> classesWithCircularReference = new HashSet<Class<? extends PersistableBusinessObject>>(); 183 184 // resolve children for each parent and verify the parent does not appear as a child to itself 185 for (Class<? extends PersistableBusinessObject> parent : parentChildren.keySet()) { 186 boolean circularReferenceFound = checkChildrenForParentReference(parent, parentChildren.get(parent), parentChildren, new HashSet<Class<? extends PersistableBusinessObject>>()); 187 if (circularReferenceFound) { 188 classesWithCircularReference.add(parent); 189 } 190 } 191 192 if (!classesWithCircularReference.isEmpty()) { 193 String error = "Circular reference found for class(s): " + StringUtils.join(classesWithCircularReference, ", "); 194 LOG.error(error); 195 throw new RuntimeException(error); 196 } 197 } 198 199 /** 200 * Recursively checks all children of children who are parents for reference to the given parent class 201 * 202 * @param parent Class of parent to check for 203 * @param children Set of children classes to check 204 * @param parentChildren Map with parent class as the key and its children classes as value 205 * @param checkedParents Set of parent classes we have already checked (to prevent endless recursiveness) 206 * @return true if the parent class was found in one of the children list (meaning we have a circular reference), false 207 * otherwise 208 */ 209 protected boolean checkChildrenForParentReference(Class<? extends PersistableBusinessObject> parent, Set<Class<? extends PersistableBusinessObject>> children, Map<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>> parentChildren, Set<Class<? extends PersistableBusinessObject>> checkedParents) { 210 // if parent is in child list then we have a circular reference 211 if (children.contains(parent)) { 212 return true; 213 } 214 215 // iterate through children and check if the child is also a parent, if so then need to check its children 216 for (Class<? extends PersistableBusinessObject> child : children) { 217 if (parentChildren.containsKey(child) && !checkedParents.contains(child)) { 218 checkedParents.add(child); 219 Set<Class<? extends PersistableBusinessObject>> childChildren = parentChildren.get(child); 220 221 boolean foundParent = checkChildrenForParentReference(parent, childChildren, parentChildren, checkedParents); 222 if (foundParent) { 223 return true; 224 } 225 } 226 } 227 228 return false; 229 } 230 231 /** 232 * Helper method to build a Map with Parent classes as the key and their Set of child classes as the value 233 * 234 * @return Map<Class, Set<Class>> of parent to children classes 235 */ 236 protected Map<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>> getParentChildrenMap() { 237 Map<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>> parentChildren = new HashMap<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>>(); 238 239 for (FiscalYearMaker fiscalYearMakerHelper : fiscalYearMakers) { 240 for (Class<? extends PersistableBusinessObject> parentClass : fiscalYearMakerHelper.getParentClasses()) { 241 Set<Class<? extends PersistableBusinessObject>> children = new HashSet<Class<? extends PersistableBusinessObject>>(); 242 if (parentChildren.containsKey(parentClass)) { 243 children = parentChildren.get(parentClass); 244 } 245 children.add(fiscalYearMakerHelper.getBusinessObjectClass()); 246 247 parentChildren.put(parentClass, children); 248 } 249 } 250 251 return parentChildren; 252 } 253 254 /** 255 * Checks if the given class is a parent (to at least one other class) 256 * 257 * @param businessObjectClass class to check 258 * @return true if class is a parent, false otherwise 259 */ 260 protected boolean isParentClass(Class<? extends PersistableBusinessObject> businessObjectClass) { 261 for (FiscalYearMaker fiscalYearMakerHelper : fiscalYearMakers) { 262 for (Class<? extends PersistableBusinessObject> parentClass : fiscalYearMakerHelper.getParentClasses()) { 263 if (businessObjectClass.isAssignableFrom(parentClass)) { 264 return true; 265 } 266 } 267 } 268 269 return false; 270 } 271 272 273 /** 274 * Gets all classes that are child of another class in the given Map 275 * 276 * @param parentChildren Map with parent class as the key and its children classes as value 277 * @return Set of classes that are a child of another class 278 */ 279 protected Set<Class<? extends PersistableBusinessObject>> getChildren(Map<Class<? extends PersistableBusinessObject>, Set<Class<? extends PersistableBusinessObject>>> parentChildren) { 280 Set<Class<? extends PersistableBusinessObject>> children = new HashSet<Class<? extends PersistableBusinessObject>>(); 281 282 for (Class<? extends PersistableBusinessObject> parentClass : parentChildren.keySet()) { 283 children.addAll(parentChildren.get(parentClass)); 284 } 285 286 return children; 287 } 288 289 /** 290 * Helper method to build a Map with the copy class as the key and its corresponding <code>FiscalYearMaker</code> as the Map 291 * value 292 * 293 * @return Map<Class, FiscalYearMaker> of copy classes to FiscalYearMaker objects 294 */ 295 protected Map<Class<? extends PersistableBusinessObject>, FiscalYearMaker> getFiscalYearMakerMap() { 296 Map<Class<? extends PersistableBusinessObject>, FiscalYearMaker> fiscalYearMakerMap = new HashMap<Class<? extends PersistableBusinessObject>, FiscalYearMaker>(); 297 298 for (FiscalYearMaker fiscalYearMakerHelper : fiscalYearMakers) { 299 fiscalYearMakerMap.put(fiscalYearMakerHelper.getBusinessObjectClass(), fiscalYearMakerHelper); 300 } 301 302 return fiscalYearMakerMap; 303 } 304 305 /** 306 * Validates each configured fiscal year maker implementation 307 */ 308 protected void validateFiscalYearMakerConfiguration() { 309 Set<Class<? extends PersistableBusinessObject>> businessObjectClasses = new HashSet<Class<? extends PersistableBusinessObject>>(); 310 311 for (FiscalYearMaker fiscalYearMaker : fiscalYearMakers) { 312 Class<? extends PersistableBusinessObject> businessObjectClass = fiscalYearMaker.getBusinessObjectClass(); 313 if (businessObjectClass == null) { 314 String error = "Business object class is null for fiscal year maker"; 315 LOG.error(error); 316 throw new RuntimeException(error); 317 } 318 319 if (!PersistableBusinessObject.class.isAssignableFrom(businessObjectClass)) { 320 String error = String.format("Business object class %s does not implement %s", businessObjectClass.getName(), PersistableBusinessObject.class.getName()); 321 LOG.error(error); 322 throw new RuntimeException(error); 323 } 324 325 if (businessObjectClasses.contains(businessObjectClass)) { 326 String error = String.format("Business object class %s has two fiscal year maker implementations defined", businessObjectClass.getName()); 327 LOG.error(error); 328 throw new RuntimeException(error); 329 } 330 331 businessObjectClasses.add(businessObjectClass); 332 } 333 334 // validate parents are in copy list 335 Set<Class<? extends PersistableBusinessObject>> parentsNotInCopyList = new HashSet<Class<? extends PersistableBusinessObject>>(); 336 for (FiscalYearMaker fiscalYearMaker : fiscalYearMakers) { 337 parentsNotInCopyList.addAll(CollectionUtils.subtract(fiscalYearMaker.getParentClasses(), businessObjectClasses)); 338 } 339 340 if (!parentsNotInCopyList.isEmpty()) { 341 String error = "Parent classes not in copy list: " + StringUtils.join(parentsNotInCopyList, ","); 342 LOG.error(error); 343 throw new RuntimeException(error); 344 } 345 } 346 347 /** 348 * Write outs errors encountered while creating new records for an object to LOG. 349 * 350 * @param copyErrors Collection of error messages to write 351 */ 352 protected void writeCopyFailureMessages(Collection<String> copyErrors) { 353 if (!copyErrors.isEmpty()) { 354 LOG.warn("\n"); 355 for (String copyError : copyErrors) { 356 LOG.warn(String.format("\n%s", copyError)); 357 } 358 LOG.warn("\n"); 359 } 360 } 361 362 /** 363 * Populates the fiscal year maker list from the installed modules 364 */ 365 public void initialize() { 366 fiscalYearMakers = new ArrayList<FiscalYearMaker>(); 367 for (ModuleService moduleService : kualiModuleService.getInstalledModuleServices()) { 368 if (moduleService.getModuleConfiguration() instanceof FinancialSystemModuleConfiguration) { 369 fiscalYearMakers.addAll(((FinancialSystemModuleConfiguration) moduleService.getModuleConfiguration()).getFiscalYearMakers()); 370 } 371 } 372 } 373 374 /** 375 * Sets the fiscalYearMakers attribute value. 376 * 377 * @param fiscalYearMakers The fiscalYearMakers to set. 378 */ 379 protected void setFiscalYearMakers(List<FiscalYearMaker> fiscalYearMakers) { 380 this.fiscalYearMakers = fiscalYearMakers; 381 } 382 383 /** 384 * Sets the parameterService attribute value. 385 * 386 * @param parameterService The parameterService to set. 387 */ 388 public void setParameterService(ParameterService parameterService) { 389 this.parameterService = parameterService; 390 } 391 392 /** 393 * Sets the fiscalYearMakersDao attribute value. 394 * 395 * @param fiscalYearMakersDao The fiscalYearMakersDao to set. 396 */ 397 public void setFiscalYearMakersDao(FiscalYearMakersDao fiscalYearMakersDao) { 398 this.fiscalYearMakersDao = fiscalYearMakersDao; 399 } 400 401 /** 402 * Sets the kualiModuleService attribute value. 403 * 404 * @param kualiModuleService The kualiModuleService to set. 405 */ 406 public void setKualiModuleService(KualiModuleService kualiModuleService) { 407 this.kualiModuleService = kualiModuleService; 408 } 409 410 /** 411 * Gets the fiscalYearMakersDao attribute. 412 * 413 * @return Returns the fiscalYearMakersDao. 414 */ 415 protected FiscalYearMakersDao getFiscalYearMakersDao() { 416 return fiscalYearMakersDao; 417 } 418 419 /** 420 * Gets the parameterService attribute. 421 * 422 * @return Returns the parameterService. 423 */ 424 protected ParameterService getParameterService() { 425 return parameterService; 426 } 427 428 /** 429 * Gets the kualiModuleService attribute. 430 * 431 * @return Returns the kualiModuleService. 432 */ 433 protected KualiModuleService getKualiModuleService() { 434 return kualiModuleService; 435 } 436 437 /** 438 * Gets the fiscalYearMakers attribute. 439 * 440 * @return Returns the fiscalYearMakers. 441 */ 442 protected List<FiscalYearMaker> getFiscalYearMakers() { 443 return fiscalYearMakers; 444 } 445 446 }