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    }