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 }