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