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}