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