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.dataaccess.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.beanutils.PropertyUtils; 027 import org.apache.commons.lang.StringUtils; 028 import org.apache.log4j.Logger; 029 import org.apache.ojb.broker.query.QueryByCriteria; 030 import org.apache.ojb.broker.query.ReportQueryByCriteria; 031 import org.apache.ojb.broker.util.ObjectModification; 032 import org.kuali.kfs.coa.businessobject.AccountingPeriod; 033 import org.kuali.kfs.sys.batch.dataaccess.FiscalYearMaker; 034 import org.kuali.kfs.sys.batch.dataaccess.FiscalYearMakersDao; 035 import org.kuali.rice.kns.bo.PersistableBusinessObject; 036 import org.kuali.rice.kns.dao.impl.PlatformAwareDaoBaseOjb; 037 import org.kuali.rice.kns.service.BusinessObjectService; 038 import org.kuali.rice.kns.service.PersistenceStructureService; 039 import org.kuali.rice.kns.util.ObjectUtils; 040 041 /** 042 * @see org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakersDao 043 */ 044 public class FiscalYearMakersDaoOjb extends PlatformAwareDaoBaseOjb implements FiscalYearMakersDao { 045 private static Logger LOG = org.apache.log4j.Logger.getLogger(FiscalYearMakersDaoOjb.class); 046 047 protected static final String KEY_STRING_DELIMITER = "|"; 048 049 private PersistenceStructureService persistenceStructureService; 050 private BusinessObjectService businessObjectService; 051 052 /** 053 * @see org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakersDao#deleteNewYearRows(java.lang.Integer, 054 * org.kuali.kfs.coa.batch.dataaccess.FiscalYearMakerHelper) 055 */ 056 public void deleteNewYearRows(Integer baseYear, FiscalYearMaker objectFiscalYearMaker) { 057 LOG.info(String.format("\ndeleting %s for target year(s)", objectFiscalYearMaker.getBusinessObjectClass().getName())); 058 059 QueryByCriteria queryID = new QueryByCriteria(objectFiscalYearMaker.getBusinessObjectClass(), objectFiscalYearMaker.createDeleteCriteria(baseYear)); 060 getPersistenceBrokerTemplate().deleteByQuery(queryID); 061 062 getPersistenceBrokerTemplate().clearCache(); 063 } 064 065 /** 066 * @see org.kuali.kfs.sys.batch.dataaccess.FiscalYearMakersDao#createNewYearRows(java.lang.Integer, 067 * org.kuali.kfs.sys.batch.dataaccess.FiscalYearMaker, boolean, java.util.Map) 068 */ 069 public Collection<String> createNewYearRows(Integer baseYear, FiscalYearMaker objectFiscalYearMaker, boolean replaceMode, Map<Class<? extends PersistableBusinessObject>, Set<String>> parentKeysWritten, boolean isParentClass) { 070 LOG.info(String.format("\n copying %s from %d to %d", objectFiscalYearMaker.getBusinessObjectClass(), baseYear, baseYear + 1)); 071 072 Integer rowsRead = new Integer(0); 073 Integer rowsWritten = new Integer(0); 074 Integer rowsFailingRI = new Integer(0); 075 076 // list of copy error messages to be written out at end 077 Collection<String> copyErrors = new ArrayList<String>(); 078 079 // Set of primary key strings written 080 Set<String> keysWritten = new HashSet<String>(); 081 082 // retrieve base year records to copy 083 QueryByCriteria queryId = new QueryByCriteria(objectFiscalYearMaker.getBusinessObjectClass(), objectFiscalYearMaker.createSelectionCriteria(baseYear)); 084 Collection<PersistableBusinessObject> recordsToCopy = getPersistenceBrokerTemplate().getCollectionByQuery(queryId); 085 for (PersistableBusinessObject objectToCopy : recordsToCopy) { 086 rowsRead = rowsRead + 1; 087 088 // remove reference/collection fields so they will not cause an issue with the insert 089 removeNonPrimitiveFields(objectToCopy); 090 091 // set record fields for new year 092 objectFiscalYearMaker.changeForNewYear(baseYear, objectToCopy); 093 094 // determine if new year record already exists and if so do not overwrite 095 PersistableBusinessObject foundRecord = businessObjectService.retrieve(objectToCopy); 096 if (foundRecord != null) { 097 addToKeysWritten(objectToCopy, keysWritten); 098 continue; 099 } 100 101 // check parent records exist so RI will be satisfied 102 boolean allParentRecordsExist = validateParentRecordsExist(objectFiscalYearMaker, objectToCopy, parentKeysWritten, copyErrors); 103 if (!allParentRecordsExist) { 104 rowsFailingRI = rowsFailingRI + 1; 105 continue; 106 } 107 108 // store new record 109 getPersistenceBroker(true).store(objectToCopy, ObjectModification.INSERT); 110 rowsWritten = rowsWritten + 1; 111 112 addToKeysWritten(objectToCopy, keysWritten); 113 } 114 115 if (isParentClass) { 116 parentKeysWritten.put(objectFiscalYearMaker.getBusinessObjectClass(), keysWritten); 117 } 118 119 LOG.warn(String.format("\n%s:\n%d read = %d\n%d written = %d\nfailed RI = %d", objectFiscalYearMaker.getBusinessObjectClass(), baseYear, rowsRead, baseYear + 1, rowsWritten, rowsFailingRI)); 120 121 getPersistenceBrokerTemplate().clearCache(); 122 123 return copyErrors; 124 } 125 126 /** 127 * Sets all reference and collection fields defined in the persistence layer to null on the given object 128 * 129 * @param businessObject object to set properties for 130 */ 131 protected void removeNonPrimitiveFields(PersistableBusinessObject businessObject) { 132 try { 133 Map<String, Class> referenceFields = persistenceStructureService.listReferenceObjectFields(businessObject); 134 for (String fieldName : referenceFields.keySet()) { 135 ObjectUtils.setObjectProperty(businessObject, fieldName, null); 136 } 137 138 Map<String, Class> collectionFields = persistenceStructureService.listCollectionObjectTypes(businessObject); 139 for (String fieldName : collectionFields.keySet()) { 140 ObjectUtils.setObjectProperty(businessObject, fieldName, null); 141 } 142 } 143 catch (Exception e) { 144 throw new RuntimeException("Unable to set non primitive fields to null: " + e.getMessage(), e); 145 } 146 } 147 148 /** 149 * Checks all parents for the object we are copying has a corresponding record for the child record 150 * 151 * @return true if all parent records exist, false otherwise 152 */ 153 protected boolean validateParentRecordsExist(FiscalYearMaker objectFiscalYearMaker, PersistableBusinessObject childRecord, Map<Class<? extends PersistableBusinessObject>, Set<String>> parentKeysWritten, Collection<String> copyErrors) { 154 boolean allParentRecordsExist = true; 155 156 // iterate through all parents, get attribute reference name and attempt to retrieve 157 for (Class<? extends PersistableBusinessObject> parentClass : objectFiscalYearMaker.getParentClasses()) { 158 allParentRecordsExist &= validateChildParentReferencesExist(childRecord, parentClass, parentKeysWritten.get(parentClass), copyErrors); 159 } 160 161 return allParentRecordsExist; 162 } 163 164 /** 165 * Validates the parent record(s) exists for the child record by retrieving the OJB reference (if found and foreign keys have 166 * value) 167 * 168 * @param childRecord child record we are inserting 169 * @param parentClass class for parent of child 170 * @param parentKeys Set of parent key Strings that have been written 171 * @param copyErrors Collection for adding error messages 172 * @return true if the parent record(s) exist, false otherwise 173 */ 174 protected boolean validateChildParentReferencesExist(PersistableBusinessObject childRecord, Class<? extends PersistableBusinessObject> parentClass, Set<String> parentKeys, Collection<String> copyErrors) { 175 boolean allChildParentReferencesExist = true; 176 boolean foundParentReference = false; 177 178 // get all references for child class 179 Map<String, Class> referenceObjects = persistenceStructureService.listReferenceObjectFields(childRecord.getClass()); 180 181 // iterate through to find references with the parent class 182 for (String referenceName : referenceObjects.keySet()) { 183 Class<? extends PersistableBusinessObject> referenceClass = referenceObjects.get(referenceName); 184 185 if (parentClass.isAssignableFrom(referenceClass)) { 186 foundParentReference = true; 187 188 String foreignKeyString = getForeignKeyStringForReference(childRecord, referenceName); 189 if (StringUtils.isNotBlank(foreignKeyString) && !parentKeys.contains(foreignKeyString)) { 190 // attempt to retrieve the parent reference in case it already existed 191 childRecord.refreshReferenceObject(referenceName); 192 PersistableBusinessObject reference = (PersistableBusinessObject) ObjectUtils.getPropertyValue(childRecord, referenceName); 193 if (ObjectUtils.isNull(reference)) { 194 allChildParentReferencesExist = false; 195 writeMissingParentCopyError(childRecord, parentClass, foreignKeyString, copyErrors); 196 } 197 else { 198 parentKeys.add(foreignKeyString); 199 } 200 } 201 } 202 } 203 204 if (!foundParentReference) { 205 LOG.warn(String.format("\n!!! NO relationships between child %s and parent %s found in OJB descriptor\n", childRecord.getClass().getName(), parentClass.getName())); 206 } 207 208 return allChildParentReferencesExist; 209 } 210 211 /** 212 * Builds a String containing foreign key values for the given reference of the business object 213 * 214 * @param businessObject business object instance with reference 215 * @param referenceName name of reference 216 * @return String of foreign key values or null if any of the foreign key values are null 217 */ 218 protected String getForeignKeyStringForReference(PersistableBusinessObject businessObject, String referenceName) { 219 Map<String, String> foreignKeyToPrimaryKeyMap = persistenceStructureService.getForeignKeysForReference(businessObject.getClass(), referenceName); 220 221 String foreignKeyString = ""; 222 for (String fkFieldName : foreignKeyToPrimaryKeyMap.keySet()) { 223 Object fkFieldValue = ObjectUtils.getPropertyValue(businessObject, fkFieldName); 224 if (fkFieldValue != null) { 225 foreignKeyString += fkFieldValue.toString() + KEY_STRING_DELIMITER; 226 } 227 else { 228 foreignKeyString = null; 229 break; 230 } 231 } 232 233 return foreignKeyString; 234 } 235 236 /** 237 * Builds an error message when a parent record was not found for the child 238 * 239 * @param childRecord child record we are inserting 240 * @param parentClass class for parent of child 241 * @param foreignKeyString string of foreign key values that was not found in parent 242 * @param copyErrors Collection for adding error messages 243 */ 244 protected void writeMissingParentCopyError(PersistableBusinessObject childRecord, Class<? extends PersistableBusinessObject> parentClass, String foreignKeyString, Collection<String> copyErrors) { 245 StringBuilder errorCopyFailedMessage = new StringBuilder(); 246 errorCopyFailedMessage.append(childRecord.getClass().getName()); 247 errorCopyFailedMessage.append(" row for " + childRecord.toString()); 248 errorCopyFailedMessage.append(" - " + foreignKeyString); 249 errorCopyFailedMessage.append(" not in "); 250 errorCopyFailedMessage.append(parentClass.getName()); 251 252 copyErrors.add(errorCopyFailedMessage.toString()); 253 } 254 255 /** 256 * Builds a string from the primary key values and adds to given set 257 * 258 * @param copiedObject object to grab key values for 259 * @param keysWritten Set containing all pk strings 260 */ 261 protected void addToKeysWritten(PersistableBusinessObject copiedObject, Set<String> keysWritten) { 262 String keyString = ""; 263 264 List<String> keyFieldNames = persistenceStructureService.getPrimaryKeys(copiedObject.getClass()); 265 for (String keyFieldName : keyFieldNames) { 266 keyString += ObjectUtils.getPropertyValue(copiedObject, keyFieldName) + KEY_STRING_DELIMITER; 267 } 268 269 keysWritten.add(keyString); 270 } 271 272 /** 273 * Sets the persistenceStructureService attribute value. 274 * 275 * @param persistenceStructureService The persistenceStructureService to set. 276 */ 277 public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { 278 this.persistenceStructureService = persistenceStructureService; 279 } 280 281 /** 282 * Sets the businessObjectService attribute value. 283 * 284 * @param businessObjectService The businessObjectService to set. 285 */ 286 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 287 this.businessObjectService = businessObjectService; 288 } 289 290 }