001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.gl.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.Reader;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import org.kuali.module.gl.bo.OriginEntryFull;
027: import org.kuali.module.gl.bo.OriginEntryGroup;
028: import org.kuali.module.gl.service.FileEnterpriseFeederHelperService;
029: import org.kuali.module.gl.service.OriginEntryService;
030: import org.kuali.module.gl.service.ReconciliationParserService;
031: import org.kuali.module.gl.service.ReconciliationService;
032: import org.kuali.module.gl.util.EnterpriseFeederStatusAndErrorMessagesWrapper;
033: import org.kuali.module.gl.util.ExceptionCaughtStatus;
034: import org.kuali.module.gl.util.FileReconBadLoadAbortedStatus;
035: import org.kuali.module.gl.util.FileReconOkLoadOkStatus;
036: import org.kuali.module.gl.util.Message;
037: import org.kuali.module.gl.util.OriginEntryFileIterator;
038: import org.kuali.module.gl.util.ReconciliationBlock;
039: import org.springframework.transaction.annotation.Transactional;
040:
041: /**
042: * This class reads origin entries in a flat file format, reconciles them, and loads them into the origin entry table. Note: this
043: * class is marked as transactional. Using this class from a class not annotated as {@link Transactional} allows the implementation
044: * of a transaction per file. That is, every time the feedOnFile method is called, a new transaction is created, and is committed or
045: * rolled back depending on whether the upload was successful. Note: the feeding algorithm of this service will read the data file
046: * twice to minimize memory usage.
047: */
048: @Transactional
049: public class FileEnterpriseFeederHelperServiceImpl implements
050: FileEnterpriseFeederHelperService {
051: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
052: .getLogger(FileEnterpriseFeederHelperServiceImpl.class);
053:
054: private ReconciliationParserService reconciliationParserService;
055: private ReconciliationService reconciliationService;
056: private OriginEntryService originEntryService;
057:
058: /**
059: * This method does the reading and the loading of reconciliation. Read class description. This method DOES NOT handle the
060: * deletion of the done file
061: *
062: * @param doneFile a URL that must be present. The contents may be empty
063: * @param dataFile a URL to a flat file of origin entry rows.
064: * @param reconFile a URL to the reconciliation file. See the implementation of {@link ReconciliationParserService} for file
065: * format.
066: * @param originEntryGroup the group into which the origin entries will be loaded
067: * @param feederProcessName the name of the feeder process
068: * @param reconciliationTableId the name of the block to use for reconciliation within the reconciliation file
069: * @param statusAndErrors any status information should be stored within this object
070: * @see org.kuali.module.gl.service.impl.FileEnterpriseFeederHelperService#feedOnFile(java.io.File, java.io.File, java.io.File,
071: * org.kuali.module.gl.bo.OriginEntryGroup)
072: */
073: public void feedOnFile(
074: File doneFile,
075: File dataFile,
076: File reconFile,
077: OriginEntryGroup originEntryGroup,
078: String feederProcessName,
079: String reconciliationTableId,
080: EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors) {
081: LOG.info("Processing done file: " + doneFile.getAbsolutePath());
082:
083: List<Message> errorMessages = statusAndErrors
084: .getErrorMessages();
085: BufferedReader dataFileReader = null;
086:
087: ReconciliationBlock reconciliationBlock = null;
088: Reader reconReader = null;
089: try {
090: reconReader = new FileReader(reconFile);
091: reconciliationBlock = reconciliationParserService
092: .parseReconciliationBlock(reconReader,
093: reconciliationTableId);
094: } catch (IOException e) {
095: LOG.error(
096: "IO Error occured trying to read the recon file.",
097: e);
098: errorMessages.add(new Message(
099: "IO Error occured trying to read the recon file.",
100: Message.TYPE_FATAL));
101: reconciliationBlock = null;
102: statusAndErrors
103: .setStatus(new FileReconBadLoadAbortedStatus());
104: throw new RuntimeException(e);
105: } catch (RuntimeException e) {
106: LOG.error("Error occured trying to parse the recon file.",
107: e);
108: errorMessages.add(new Message(
109: "Error occured trying to parse the recon file.",
110: Message.TYPE_FATAL));
111: reconciliationBlock = null;
112: statusAndErrors
113: .setStatus(new FileReconBadLoadAbortedStatus());
114: throw e;
115: } finally {
116: if (reconReader != null) {
117: try {
118: reconReader.close();
119: } catch (IOException e) {
120: LOG.error(
121: "Error occured trying to close recon file: "
122: + reconFile.getAbsolutePath(), e);
123: }
124: }
125: }
126:
127: try {
128: if (reconciliationBlock == null) {
129: errorMessages.add(new Message(
130: "Unable to parse reconciliation file.",
131: Message.TYPE_FATAL));
132: } else {
133: dataFileReader = new BufferedReader(new FileReader(
134: dataFile));
135: Iterator<OriginEntryFull> fileIterator = new OriginEntryFileIterator(
136: dataFileReader, false);
137: reconciliationService.reconcile(fileIterator,
138: reconciliationBlock, errorMessages);
139:
140: fileIterator = null;
141: dataFileReader.close();
142: dataFileReader = null;
143: }
144:
145: if (reconciliationProcessSucceeded(errorMessages)) {
146: // exhausted the previous iterator, open a new one up
147: dataFileReader = new BufferedReader(new FileReader(
148: dataFile));
149: Iterator<OriginEntryFull> fileIterator = new OriginEntryFileIterator(
150: dataFileReader, false);
151:
152: int count = 0;
153: while (fileIterator.hasNext()) {
154: OriginEntryFull entry = fileIterator.next();
155: entry.setGroup(originEntryGroup);
156: originEntryService.save(entry);
157: count++;
158: }
159: fileIterator = null;
160: dataFileReader.close();
161: dataFileReader = null;
162:
163: statusAndErrors
164: .setStatus(new FileReconOkLoadOkStatus());
165: } else {
166: statusAndErrors
167: .setStatus(new FileReconBadLoadAbortedStatus());
168: }
169: } catch (Exception e) {
170: LOG.error(
171: "Caught exception when reconciling/loading done file: "
172: + doneFile, e);
173: statusAndErrors.setStatus(new ExceptionCaughtStatus());
174: errorMessages.add(new Message(
175: "Caught exception attempting to reconcile/load done file: "
176: + doneFile
177: + ". File contents are NOT loaded",
178: Message.TYPE_FATAL));
179: // re-throw the exception rather than returning a value so that Spring will auto-rollback
180: if (e instanceof RuntimeException) {
181: throw (RuntimeException) e;
182: } else {
183: // Spring only rolls back when throwing a runtime exception (by default), so we throw a new exception
184: throw new RuntimeException(e);
185: }
186: } finally {
187: if (dataFileReader != null) {
188: try {
189: dataFileReader.close();
190: } catch (IOException e) {
191: LOG
192: .error(
193: "IO Exception occured trying to close connection to the data file",
194: e);
195: errorMessages
196: .add(new Message(
197: "IO Exception occured trying to close connection to the data file",
198: Message.TYPE_FATAL));
199: }
200: }
201: }
202: }
203:
204: /**
205: * Returns whether the reconciliation process succeeded by looking at the reconciliation error messages For this implementation,
206: * the reconciliation does not succeed if at least one of the error messages in the list has a type of
207: * {@link Message#TYPE_FATAL}
208: *
209: * @param errorMessages a List of errorMessages
210: * @return true if any of those error messages were fatal
211: */
212: protected boolean reconciliationProcessSucceeded(
213: List<Message> errorMessages) {
214: for (Message message : errorMessages) {
215: if (message.getType() == Message.TYPE_FATAL) {
216: return false;
217: }
218: }
219: return true;
220: }
221:
222: /**
223: * Gets the reconciliationParserService attribute.
224: *
225: * @return Returns the reconciliationParserService.
226: */
227: public ReconciliationParserService getReconciliationParserService() {
228: return reconciliationParserService;
229: }
230:
231: /**
232: * Sets the reconciliationParserService attribute value.
233: *
234: * @param reconciliationParserService The reconciliationParserService to set.
235: */
236: public void setReconciliationParserService(
237: ReconciliationParserService reconciliationParserService) {
238: this .reconciliationParserService = reconciliationParserService;
239: }
240:
241: /**
242: * Gets the reconciliationService attribute.
243: *
244: * @return Returns the reconciliationService.
245: */
246: public ReconciliationService getReconciliationService() {
247: return reconciliationService;
248: }
249:
250: /**
251: * Sets the reconciliationService attribute value.
252: *
253: * @param reconciliationService The reconciliationService to set.
254: */
255: public void setReconciliationService(
256: ReconciliationService reconciliationService) {
257: this .reconciliationService = reconciliationService;
258: }
259:
260: /**
261: * Gets the originEntryService attribute.
262: *
263: * @return Returns the originEntryService.
264: */
265: public OriginEntryService getOriginEntryService() {
266: return originEntryService;
267: }
268:
269: /**
270: * Sets the originEntryService attribute value.
271: *
272: * @param originEntryService The originEntryService to set.
273: */
274: public void setOriginEntryService(
275: OriginEntryService originEntryService) {
276: this.originEntryService = originEntryService;
277: }
278: }
|