View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.batch.dataaccess.impl;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.apache.commons.beanutils.PropertyUtils;
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.log4j.Logger;
29  import org.apache.ojb.broker.query.QueryByCriteria;
30  import org.apache.ojb.broker.query.ReportQueryByCriteria;
31  import org.apache.ojb.broker.util.ObjectModification;
32  import org.kuali.ole.sys.batch.dataaccess.FiscalYearMaker;
33  import org.kuali.ole.sys.batch.dataaccess.FiscalYearMakersDao;
34  import org.kuali.ole.sys.businessobject.FiscalYearBasedBusinessObject;
35  import org.kuali.rice.core.framework.persistence.ojb.dao.PlatformAwareDaoBaseOjb;
36  import org.kuali.rice.krad.bo.PersistableBusinessObject;
37  import org.kuali.rice.krad.util.ObjectUtils;
38  
39  /**
40   * @see org.kuali.ole.coa.batch.dataaccess.FiscalYearMakersDao
41   */
42  public class FiscalYearMakersDaoOjb extends PlatformAwareDaoBaseOjb implements FiscalYearMakersDao {
43      private static final Logger LOG = org.apache.log4j.Logger.getLogger(FiscalYearMakersDaoOjb.class);
44      
45      protected static final String KEY_STRING_DELIMITER = "|";
46  
47      /**
48       * @see org.kuali.ole.coa.batch.dataaccess.FiscalYearMakersDao#deleteNewYearRows(java.lang.Integer,
49       *      org.kuali.ole.coa.batch.dataaccess.FiscalYearMakerHelper)
50       */
51      public void deleteNewYearRows(Integer baseYear, FiscalYearMaker objectFiscalYearMaker) {
52          if ( LOG.isInfoEnabled() ) {
53              LOG.info(String.format("\ndeleting %s for target year(s)", objectFiscalYearMaker.getBusinessObjectClass().getName()));
54          }
55  
56          QueryByCriteria queryID = new QueryByCriteria(objectFiscalYearMaker.getBusinessObjectClass(), objectFiscalYearMaker.createDeleteCriteria(baseYear));
57          getPersistenceBrokerTemplate().deleteByQuery(queryID);
58  
59          getPersistenceBrokerTemplate().clearCache();
60      }
61  
62      /**
63       * @see org.kuali.ole.sys.batch.dataaccess.FiscalYearMakersDao#createNewYearRows(java.lang.Integer,
64       *      org.kuali.ole.sys.batch.dataaccess.FiscalYearMaker, boolean, java.util.Map)
65       */
66      public Collection<String> createNewYearRows(Integer baseYear, FiscalYearMaker fiscalYearMaker, boolean replaceMode, Map<Class<? extends FiscalYearBasedBusinessObject>, Set<String>> parentKeysWritten, boolean isParentClass) throws Exception {
67          if ( LOG.isInfoEnabled() ) {
68              LOG.info(String.format("\n copying %s from %d to %d", fiscalYearMaker.getBusinessObjectClass().getName(), baseYear, baseYear + 1));
69          }
70  
71          int rowsRead = 0;
72          int rowsWritten = 0;
73          int rowsFailingRI = 0;
74  
75          // list of copy error messages to be written out at end
76          List<String> copyErrors = new ArrayList<String>();
77  
78          // Set of primary key strings written
79          Set<String> keysWritten = new HashSet<String>();
80  
81          // retrieve the list of next-year PKs for the given object
82          List<String> primaryKeyFields = fiscalYearMaker.getPrimaryKeyPropertyNames();
83  
84          Set<String> nextYearPrimaryKeys = new HashSet<String>(2000);
85          LOG.info( "Loading Next Year's PKs for comparison");        
86          ReportQueryByCriteria nextYearKeyQuery = new ReportQueryByCriteria(fiscalYearMaker.getBusinessObjectClass(), primaryKeyFields.toArray(new String[0]), fiscalYearMaker.createNextYearSelectionCriteria(baseYear) );
87          Iterator<Object[]> nextYearRecords = getPersistenceBrokerTemplate().getReportQueryIteratorByQuery(nextYearKeyQuery);
88          StringBuilder keyString = new StringBuilder(40);
89          int numNextYearRecords = 0;
90          while ( nextYearRecords.hasNext() ) {
91              numNextYearRecords++;
92              keyString.setLength(0);
93              Object[] record = nextYearRecords.next();
94              for ( Object f : record ) {
95                  keyString.append( f ).append( KEY_STRING_DELIMITER );
96              }
97              nextYearPrimaryKeys.add(keyString.toString());
98              if ( numNextYearRecords % 10000 == 0 ) {
99                  if ( LOG.isInfoEnabled() ) {
100                     LOG.info("Processing Record: " + numNextYearRecords);
101                 }
102             }
103         }
104         if ( LOG.isInfoEnabled() ) {
105             LOG.info( "Completed load of next year keys.  " + numNextYearRecords + " keys loaded.");
106             LOG.info( "Starting processing of existing FY rows" );
107         }
108         // retrieve base year records to copy
109         QueryByCriteria queryId = new QueryByCriteria(fiscalYearMaker.getBusinessObjectClass(), fiscalYearMaker.createSelectionCriteria(baseYear));
110         // BIG QUERY - GET ALL RECORDS for the current FY 
111         Iterator<FiscalYearBasedBusinessObject> recordsToCopy = getPersistenceBrokerTemplate().getIteratorByQuery(queryId);
112         
113         
114         while ( recordsToCopy.hasNext() ) {
115             FiscalYearBasedBusinessObject objectToCopy = recordsToCopy.next();
116             rowsRead++;
117             if ( LOG.isInfoEnabled() ) {
118                 if ( rowsRead % 1000 == 0 ) {
119                     LOG.info( "*** Processing Record: " + rowsRead + " -- Written So Far: " + rowsWritten + " -- Failing RI: " + rowsFailingRI + " -- Keys Written: " + keysWritten.size() );
120                 }
121             }
122 
123             // remove reference/collection fields so they will not cause an issue with the insert
124             removeNonPrimitiveFields(fiscalYearMaker, objectToCopy);
125 
126             // set record fields for new year
127             fiscalYearMaker.changeForNewYear(baseYear, objectToCopy);
128 
129             // determine if new year record already exists and if so do not overwrite
130             if ( nextYearPrimaryKeys.contains(getKeyString(fiscalYearMaker, primaryKeyFields, objectToCopy)) ) {
131                 if (isParentClass) {
132                     addToKeysWritten(fiscalYearMaker, primaryKeyFields, objectToCopy, keysWritten);
133                 }
134                 continue;
135             }
136 
137             // check parent records exist so RI will be satisfied
138             if (!validateParentRecordsExist(fiscalYearMaker, objectToCopy, parentKeysWritten, copyErrors)) {
139                 rowsFailingRI++;
140                 continue;
141             }
142 
143             // store new record
144             getPersistenceBroker(true).store(objectToCopy, ObjectModification.INSERT);
145             rowsWritten++;
146             if (isParentClass) {
147                 addToKeysWritten(fiscalYearMaker, primaryKeyFields, objectToCopy, keysWritten);
148             }
149         }
150 
151         if (isParentClass) {
152             parentKeysWritten.put(fiscalYearMaker.getBusinessObjectClass(), keysWritten);
153         }
154 
155         if ( LOG.isInfoEnabled() ) {
156             LOG.info(String.format("\n%s:\n%d read = %d\n%d written = %d\nfailed RI = %d", fiscalYearMaker.getBusinessObjectClass(), baseYear, rowsRead, baseYear + 1, rowsWritten, rowsFailingRI));
157         }
158 
159         getPersistenceBrokerTemplate().clearCache();
160 
161         return copyErrors;
162     }
163 
164     /**
165      * Sets all reference and collection fields defined in the persistence layer to null on the given object
166      * 
167      * @param businessObject object to set properties for
168      */
169     protected void removeNonPrimitiveFields( FiscalYearMaker fiscalYearMaker, FiscalYearBasedBusinessObject businessObject) {
170         try {
171             @SuppressWarnings("rawtypes")
172             Map<String, Class> referenceFields = fiscalYearMaker.getReferenceObjectProperties();
173             for (String fieldName : referenceFields.keySet()) {
174                 if (!fieldName.equals("extension")) {
175                 PropertyUtils.setSimpleProperty(businessObject, fieldName, null);
176             }
177             }
178 
179             @SuppressWarnings("rawtypes")
180             Map<String, Class> collectionFields = fiscalYearMaker.getCollectionProperties();
181             for (String fieldName : collectionFields.keySet()) {
182                 PropertyUtils.setSimpleProperty(businessObject, fieldName, null);
183             }
184         } catch (Exception e) {
185             throw new RuntimeException("Unable to set non primitive fields to null: " + e.getMessage(), e);
186         }
187     }
188 
189     /**
190      * Checks all parents for the object we are copying has a corresponding record for the child record
191      * 
192      * @return true if all parent records exist, false otherwise
193      */
194     protected boolean validateParentRecordsExist(FiscalYearMaker objectFiscalYearMaker, FiscalYearBasedBusinessObject childRecord, Map<Class<? extends FiscalYearBasedBusinessObject>, Set<String>> parentKeysWritten, List<String> copyErrors) throws Exception {
195         // iterate through all parents, get attribute reference name and attempt to retrieve
196         for (Class<? extends FiscalYearBasedBusinessObject> parentClass : objectFiscalYearMaker.getParentClasses()) {
197             if ( !validateChildParentReferencesExist(objectFiscalYearMaker,childRecord, parentClass, parentKeysWritten.get(parentClass), copyErrors) ) {
198                 return false;
199             }
200         }
201 
202         return true;
203     }
204 
205     /**
206      * Validates the parent record(s) exists for the child record by retrieving the OJB reference (if found and foreign keys have
207      * value)
208      * 
209      * @param childRecord child record we are inserting
210      * @param parentClass class for parent of child
211      * @param parentKeys Set of parent key Strings that have been written
212      * @param copyErrors Collection for adding error messages
213      * @return true if the parent record(s) exist, false otherwise
214      */
215     protected boolean validateChildParentReferencesExist(FiscalYearMaker objectFiscalYearMaker,FiscalYearBasedBusinessObject childRecord, Class<? extends FiscalYearBasedBusinessObject> parentClass, Set<String> parentKeys, List<String> copyErrors) throws Exception {
216         boolean allChildParentReferencesExist = true;
217         boolean foundParentReference = false;
218 
219         // get all references for child class
220         @SuppressWarnings("rawtypes")
221         Map<String, Class> referenceObjects = objectFiscalYearMaker.getReferenceObjectProperties();
222 
223         // iterate through to find references with the parent class
224         for (String referenceName : referenceObjects.keySet()) {
225             Class<? extends PersistableBusinessObject> referenceClass = referenceObjects.get(referenceName);
226 
227             if (parentClass.isAssignableFrom(referenceClass)) {
228                 foundParentReference = true;
229 
230                 String foreignKeyString = getForeignKeyStringForReference(objectFiscalYearMaker, childRecord, referenceName);
231                 if (StringUtils.isNotBlank(foreignKeyString) 
232                         && !parentKeys.contains(foreignKeyString)) {
233                     // attempt to retrieve the parent reference in case it already existed
234                     getPersistenceBroker(true).retrieveReference(childRecord, referenceName);
235                     PersistableBusinessObject reference = (PersistableBusinessObject) PropertyUtils.getSimpleProperty(childRecord, referenceName);
236                     if (ObjectUtils.isNull(reference)) {
237                         allChildParentReferencesExist = false;
238                         writeMissingParentCopyError(childRecord, parentClass, foreignKeyString, copyErrors);
239                         LOG.warn( "Missing Parent Object: " + copyErrors.get(copyErrors.size()-1));
240                     } else {
241                         parentKeys.add(foreignKeyString);
242                     }
243                 }
244             }
245         }
246 
247         if (!foundParentReference) {
248             LOG.warn(String.format("\n!!! NO relationships between child %s and parent %s found in OJB descriptor\n", childRecord.getClass().getName(), parentClass.getName()));
249         }
250 
251         return allChildParentReferencesExist;
252     }
253 
254     /**
255      * Builds a String containing foreign key values for the given reference of the business object
256      * 
257      * @param businessObject business object instance with reference
258      * @param referenceName name of reference
259      * @return String of foreign key values or null if any of the foreign key values are null
260      */
261     protected String getForeignKeyStringForReference( FiscalYearMaker fiscalYearMaker, FiscalYearBasedBusinessObject businessObject, String referenceName) throws Exception {
262         Map<String, String> foreignKeyToPrimaryKeyMap = fiscalYearMaker.getForeignKeyMappings( referenceName );
263 
264         StringBuilder foreignKeyString = new StringBuilder(80);
265         for (String fkFieldName : foreignKeyToPrimaryKeyMap.keySet()) {
266             Object fkFieldValue = PropertyUtils.getSimpleProperty(businessObject, fkFieldName);
267             if (fkFieldValue != null) {
268                 foreignKeyString.append( fkFieldValue.toString() ).append( KEY_STRING_DELIMITER );
269             } else {
270                 foreignKeyString.setLength(0);
271                 break;
272             }
273         }
274 
275         return foreignKeyString.toString();
276     }
277 
278     /**
279      * Builds an error message when a parent record was not found for the child
280      * 
281      * @param childRecord child record we are inserting
282      * @param parentClass class for parent of child
283      * @param foreignKeyString string of foreign key values that was not found in parent
284      * @param copyErrors Collection for adding error messages
285      */
286     protected void writeMissingParentCopyError(FiscalYearBasedBusinessObject childRecord, Class<? extends FiscalYearBasedBusinessObject> parentClass, String foreignKeyString, Collection<String> copyErrors) {
287         StringBuilder errorCopyFailedMessage = new StringBuilder(150);
288         errorCopyFailedMessage.append(childRecord.getClass().getName());
289         errorCopyFailedMessage.append(" row for " + childRecord.toString());
290         errorCopyFailedMessage.append(" - " + foreignKeyString);
291         errorCopyFailedMessage.append(" not in ");
292         errorCopyFailedMessage.append(parentClass.getName());
293 
294         copyErrors.add(errorCopyFailedMessage.toString());
295     }
296 
297     /**
298      * Builds a string from the primary key values and adds to given set
299      * 
300      * @param copiedObject object to grab key values for
301      * @param keysWritten Set containing all pk strings
302      */
303     protected void addToKeysWritten( FiscalYearMaker fiscalYearMaker, List<String> keyFieldNames, FiscalYearBasedBusinessObject copiedObject, Set<String> keysWritten) throws Exception {
304         keysWritten.add(getKeyString(fiscalYearMaker, keyFieldNames, copiedObject));
305     }
306 
307     protected String getKeyString( FiscalYearMaker fiscalYearMaker, List<String> keyFieldNames, FiscalYearBasedBusinessObject businessObject ) throws Exception {
308         StringBuilder keyString = new StringBuilder(40);
309         for (String keyFieldName : keyFieldNames) {
310             keyString.append( PropertyUtils.getSimpleProperty(businessObject, keyFieldName) ).append( KEY_STRING_DELIMITER );
311         }
312         return keyString.toString();
313     }
314 }