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.module.ld.batch.service.impl;
017    
018    import java.io.BufferedReader;
019    import java.io.File;
020    import java.io.FileReader;
021    import java.io.IOException;
022    import java.io.PrintStream;
023    import java.io.Reader;
024    import java.util.Iterator;
025    import java.util.List;
026    
027    import org.kuali.kfs.gl.batch.service.ReconciliationParserService;
028    import org.kuali.kfs.gl.batch.service.impl.ExceptionCaughtStatus;
029    import org.kuali.kfs.gl.batch.service.impl.FileReconBadLoadAbortedStatus;
030    import org.kuali.kfs.gl.batch.service.impl.FileReconOkLoadOkStatus;
031    import org.kuali.kfs.gl.batch.service.impl.ReconciliationBlock;
032    import org.kuali.kfs.gl.report.LedgerSummaryReport;
033    import org.kuali.kfs.gl.service.OriginEntryService;
034    import org.kuali.kfs.gl.service.impl.EnterpriseFeederStatusAndErrorMessagesWrapper;
035    import org.kuali.kfs.module.ld.batch.service.FileEnterpriseFeederHelperService;
036    import org.kuali.kfs.module.ld.batch.service.ReconciliationService;
037    import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry;
038    import org.kuali.kfs.module.ld.util.LaborOriginEntryFileIterator;
039    import org.kuali.kfs.sys.Message;
040    
041    /**
042     * This class reads origin entries in a flat file format, reconciles them, and loads them into the origin entry table. 
043     * Note: the feeding algorithm of this service will read the data file twice to minimize memory usage.
044     */
045    public class FileEnterpriseFeederHelperServiceImpl implements FileEnterpriseFeederHelperService {
046        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FileEnterpriseFeederHelperServiceImpl.class);
047    
048        private ReconciliationParserService reconciliationParserService;
049        private ReconciliationService reconciliationService;
050        private OriginEntryService originEntryService;
051    
052        /**
053         * This method does the reading and the loading of reconciliation. Read class description. This method DOES NOT handle the
054         * deletion of the done file
055         * 
056         * @param doneFile a URL that must be present. The contents may be empty
057         * @param dataFile a URL to a flat file of origin entry rows.
058         * @param reconFile a URL to the reconciliation file. See the implementation of {@link ReconciliationParserService} for file
059         *        format.
060         * @param originEntryGroup the group into which the origin entries will be loaded
061         * @param feederProcessName the name of the feeder process
062         * @param reconciliationTableId the name of the block to use for reconciliation within the reconciliation file
063         * @param statusAndErrors any status information should be stored within this object
064         * @see org.kuali.module.gl.service.impl.FileEnterpriseFeederHelperService#feedOnFile(java.io.File, java.io.File, java.io.File,
065         *      org.kuali.kfs.gl.businessobject.OriginEntryGroup)
066         */
067        public void feedOnFile(File doneFile, File dataFile, File reconFile, PrintStream enterpriseFeedPs, String feederProcessName, String reconciliationTableId, EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors, LedgerSummaryReport ledgerSummaryReport) {
068            LOG.info("Processing done file: " + doneFile.getAbsolutePath());
069    
070            List<Message> errorMessages = statusAndErrors.getErrorMessages();
071            BufferedReader dataFileReader = null;
072    
073            ReconciliationBlock reconciliationBlock = null;
074            Reader reconReader = null;
075            try {
076                reconReader = new FileReader(reconFile);
077                reconciliationBlock = reconciliationParserService.parseReconciliationBlock(reconReader, reconciliationTableId);
078            }
079            catch (IOException e) {
080                LOG.error("IO Error occured trying to read the recon file.", e);
081                errorMessages.add(new Message("IO Error occured trying to read the recon file.", Message.TYPE_FATAL));
082                reconciliationBlock = null;
083                statusAndErrors.setStatus(new FileReconBadLoadAbortedStatus());
084                throw new RuntimeException(e);
085            }
086            catch (RuntimeException e) {
087                LOG.error("Error occured trying to parse the recon file.", e);
088                errorMessages.add(new Message("Error occured trying to parse the recon file.", Message.TYPE_FATAL));
089                reconciliationBlock = null;
090                statusAndErrors.setStatus(new FileReconBadLoadAbortedStatus());
091                throw e;
092            }
093            finally {
094                if (reconReader != null) {
095                    try {
096                        reconReader.close();
097                    }
098                    catch (IOException e) {
099                        LOG.error("Error occured trying to close recon file: " + reconFile.getAbsolutePath(), e);
100                    }
101                }
102            }
103    
104            try {
105                if (reconciliationBlock == null) {
106                    errorMessages.add(new Message("Unable to parse reconciliation file.", Message.TYPE_FATAL));
107                }
108                else {
109                    dataFileReader = new BufferedReader(new FileReader(dataFile));
110                    Iterator<LaborOriginEntry> fileIterator = new LaborOriginEntryFileIterator(dataFileReader, false);
111                    reconciliationService.reconcile(fileIterator, reconciliationBlock, errorMessages);
112    
113                    fileIterator = null;
114                    dataFileReader.close();
115                    dataFileReader = null;
116                }
117    
118                if (reconciliationProcessSucceeded(errorMessages)) {
119                    dataFileReader = new BufferedReader(new FileReader(dataFile));
120                    String line;
121                    int count = 0;
122                    
123                    // create an entry to temporarily parse each line as it comes in
124                    LaborOriginEntry tempEntry = new LaborOriginEntry();
125                    while ((line = dataFileReader.readLine()) != null) {
126                        try {
127                            enterpriseFeedPs.printf("%s\n", line);
128                            tempEntry.setFromTextFileForBatch(line, count);
129                            ledgerSummaryReport.summarizeEntry(tempEntry);
130                        } catch (Exception e) {
131                            throw new IOException(e.toString());
132                        }
133                        
134                        count++;
135                    }
136                    dataFileReader.close();
137                    dataFileReader = null;
138    
139                    statusAndErrors.setStatus(new FileReconOkLoadOkStatus());
140                }
141                else {
142                    statusAndErrors.setStatus(new FileReconBadLoadAbortedStatus());
143                }
144            }
145            catch (Exception e) {
146                LOG.error("Caught exception when reconciling/loading done file: " + doneFile, e);
147                statusAndErrors.setStatus(new ExceptionCaughtStatus());
148                errorMessages.add(new Message("Caught exception attempting to reconcile/load done file: " + doneFile + ".  File contents are NOT loaded", Message.TYPE_FATAL));
149                // re-throw the exception rather than returning a value so that Spring will auto-rollback
150                if (e instanceof RuntimeException) {
151                    throw (RuntimeException) e;
152                }
153                else {
154                    // Spring only rolls back when throwing a runtime exception (by default), so we throw a new exception
155                    throw new RuntimeException(e);
156                }
157            }
158            finally {
159                if (dataFileReader != null) {
160                    try {
161                        dataFileReader.close();
162                    }
163                    catch (IOException e) {
164                        LOG.error("IO Exception occured trying to close connection to the data file", e);
165                        errorMessages.add(new Message("IO Exception occured trying to close connection to the data file", Message.TYPE_FATAL));
166                    }
167                }
168            }
169        }
170    
171        /**
172         * Returns whether the reconciliation process succeeded by looking at the reconciliation error messages For this implementation,
173         * the reconciliation does not succeed if at least one of the error messages in the list has a type of
174         * {@link Message#TYPE_FATAL}
175         * 
176         * @param errorMessages a List of errorMessages
177         * @return true if any of those error messages were fatal
178         */
179        protected boolean reconciliationProcessSucceeded(List<Message> errorMessages) {
180            for (Message message : errorMessages) {
181                if (message.getType() == Message.TYPE_FATAL) {
182                    return false;
183                }
184            }
185            return true;
186        }
187    
188        /**
189         * Gets the reconciliationParserService attribute.
190         * 
191         * @return Returns the reconciliationParserService.
192         */
193        public ReconciliationParserService getReconciliationParserService() {
194            return reconciliationParserService;
195        }
196    
197        /**
198         * Sets the reconciliationParserService attribute value.
199         * 
200         * @param reconciliationParserService The reconciliationParserService to set.
201         */
202        public void setReconciliationParserService(ReconciliationParserService reconciliationParserService) {
203            this.reconciliationParserService = reconciliationParserService;
204        }
205    
206        /**
207         * Gets the reconciliationService attribute.
208         * 
209         * @return Returns the reconciliationService.
210         */
211        public ReconciliationService getReconciliationService() {
212            return reconciliationService;
213        }
214    
215        /**
216         * Sets the reconciliationService attribute value.
217         * 
218         * @param reconciliationService The reconciliationService to set.
219         */
220        public void setReconciliationService(ReconciliationService reconciliationService) {
221            this.reconciliationService = reconciliationService;
222        }
223    
224        /**
225         * Gets the originEntryService attribute.
226         * 
227         * @return Returns the originEntryService.
228         */
229        public OriginEntryService getOriginEntryService() {
230            return originEntryService;
231        }
232    
233        /**
234         * Sets the originEntryService attribute value.
235         * 
236         * @param originEntryService The originEntryService to set.
237         */
238        public void setOriginEntryService(OriginEntryService originEntryService) {
239            this.originEntryService = originEntryService;
240        }
241    }