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.File;
019 import java.io.FileFilter;
020 import java.io.FileNotFoundException;
021 import java.io.IOException;
022 import java.io.PrintStream;
023 import java.util.ArrayList;
024 import java.util.Arrays;
025 import java.util.List;
026
027 import org.apache.commons.io.filefilter.SuffixFileFilter;
028 import org.apache.commons.lang.StringUtils;
029 import org.kuali.kfs.gl.batch.service.EnterpriseFeederNotificationService;
030 import org.kuali.kfs.gl.batch.service.impl.RequiredFilesMissingStatus;
031 import org.kuali.kfs.gl.report.LedgerSummaryReport;
032 import org.kuali.kfs.gl.service.OriginEntryGroupService;
033 import org.kuali.kfs.gl.service.impl.EnterpriseFeederStatusAndErrorMessagesWrapper;
034 import org.kuali.kfs.module.ld.LaborConstants;
035 import org.kuali.kfs.module.ld.batch.service.EnterpriseFeederService;
036 import org.kuali.kfs.module.ld.batch.service.FileEnterpriseFeederHelperService;
037 import org.kuali.kfs.sys.Message;
038 import org.kuali.kfs.sys.service.ReportWriterService;
039 import org.kuali.rice.kns.service.DateTimeService;
040
041 /**
042 * This class iterates through the files in the enterprise feeder staging directory, which is injected by Spring. Note: this class
043 * is NOT annotated as transactional. This allows the helper service, which is defined as transactional, to do a per-file
044 * transaction.
045 */
046 public class FileEnterpriseFeederServiceImpl implements EnterpriseFeederService {
047 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FileEnterpriseFeederServiceImpl.class);
048
049 private String directoryName;
050 private String laborOriginEntryDirectoryName;
051
052 private OriginEntryGroupService originEntryGroupService;
053 private DateTimeService dateTimeService;
054 private FileEnterpriseFeederHelperService fileEnterpriseFeederHelperService;
055 private EnterpriseFeederNotificationService enterpriseFeederNotificationService;
056 private String reconciliationTableId;
057
058 private ReportWriterService reportWriterService;
059
060 /**
061 * Feeds file sets in the directory whose name is returned by the invocation to getDirectoryName()
062 *
063 * @see org.kuali.kfs.gl.batch.service.EnterpriseFeederService#feed(java.lang.String)
064 */
065 public void feed(String processName, boolean performNotifications) {
066 // ensure that this feeder implementation may not be run concurrently on this JVM
067
068 // to consider: maybe use java NIO classes to perform done file locking?
069 synchronized (FileEnterpriseFeederServiceImpl.class) {
070 if (StringUtils.isBlank(directoryName)) {
071 throw new IllegalArgumentException("directoryName not set for FileEnterpriseFeederServiceImpl.");
072 }
073 FileFilter doneFileFilter = new SuffixFileFilter(DONE_FILE_SUFFIX);
074
075 File enterpriseFeedFile = null;
076 String enterpriseFeedFileName = LaborConstants.BatchFileSystem.LABOR_ENTERPRISE_FEED + LaborConstants.BatchFileSystem.EXTENSION;
077 enterpriseFeedFile = new File(laborOriginEntryDirectoryName + File.separator + enterpriseFeedFileName);
078
079 PrintStream enterpriseFeedPs = null;
080 try {
081 enterpriseFeedPs = new PrintStream(enterpriseFeedFile);
082 } catch (FileNotFoundException e) {
083 LOG.error("enterpriseFeedFile doesn't exist " + enterpriseFeedFileName);
084 throw new RuntimeException("enterpriseFeedFile doesn't exist " + enterpriseFeedFileName);
085 }
086
087 LOG.info("New File created for enterprise feeder service run: " + enterpriseFeedFileName);
088
089 File directory = new File(directoryName);
090 if (!directory.exists() || !directory.isDirectory()) {
091 LOG.error("Directory doesn't exist and or it's not really a directory " + directoryName);
092 throw new RuntimeException("Directory doesn't exist and or it's not really a directory " + directoryName);
093 }
094
095 File[] doneFiles = directory.listFiles(doneFileFilter);
096 reorderDoneFiles(doneFiles);
097
098 LedgerSummaryReport ledgerSummaryReport = new LedgerSummaryReport();
099
100 List<EnterpriseFeederStatusAndErrorMessagesWrapper> statusAndErrorsList = new ArrayList<EnterpriseFeederStatusAndErrorMessagesWrapper>();
101
102 for (File doneFile : doneFiles) {
103 File dataFile = null;
104 File reconFile = null;
105
106
107 EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors = new EnterpriseFeederStatusAndErrorMessagesWrapper();
108 statusAndErrors.setErrorMessages(new ArrayList<Message>());
109
110
111 dataFile = getDataFile(doneFile);
112 reconFile = getReconFile(doneFile);
113
114 statusAndErrors.setFileNames(dataFile, reconFile, doneFile);
115
116 if (dataFile == null) {
117 LOG.error("Unable to find data file for done file: " + doneFile.getAbsolutePath());
118 statusAndErrors.getErrorMessages().add(new Message("Unable to find data file for done file: " + doneFile.getAbsolutePath(), Message.TYPE_FATAL));
119 statusAndErrors.setStatus(new RequiredFilesMissingStatus());
120 }
121 if (reconFile == null) {
122 LOG.error("Unable to find recon file for done file: " + doneFile.getAbsolutePath());
123 statusAndErrors.getErrorMessages().add(new Message("Unable to find recon file for done file: " + doneFile.getAbsolutePath(), Message.TYPE_FATAL));
124 statusAndErrors.setStatus(new RequiredFilesMissingStatus());
125 }
126
127 try {
128 if (dataFile != null && reconFile != null) {
129 LOG.info("Data file: " + dataFile.getAbsolutePath());
130 LOG.info("Reconciliation File: " + reconFile.getAbsolutePath());
131
132 fileEnterpriseFeederHelperService.feedOnFile(doneFile, dataFile, reconFile, enterpriseFeedPs, processName, reconciliationTableId, statusAndErrors, ledgerSummaryReport);
133 }
134 }
135 catch (RuntimeException e) {
136 // we need to be extremely resistant to a file load failing so that it doesn't prevent other files from loading
137 LOG.error("Caught exception when feeding done file: " + doneFile.getAbsolutePath());
138 }
139 finally {
140 statusAndErrorsList.add(statusAndErrors);
141 boolean doneFileDeleted = doneFile.delete();
142 if (!doneFileDeleted) {
143 statusAndErrors.getErrorMessages().add(new Message("Unable to delete done file: " + doneFile.getAbsolutePath(), Message.TYPE_FATAL));
144 }
145 if (performNotifications) {
146 enterpriseFeederNotificationService.notifyFileFeedStatus(processName, statusAndErrors.getStatus(), doneFile, dataFile, reconFile, statusAndErrors.getErrorMessages());
147 }
148 }
149 }
150
151 enterpriseFeedPs.close();
152 generateReport(statusAndErrorsList, ledgerSummaryReport, laborOriginEntryDirectoryName + File.separator + enterpriseFeedFileName);
153
154 String enterpriseFeedDoneFileName = enterpriseFeedFileName.replace(LaborConstants.BatchFileSystem.EXTENSION, LaborConstants.BatchFileSystem.DONE_FILE_EXTENSION);
155 File enterpriseFeedDoneFile = new File (laborOriginEntryDirectoryName + File.separator + enterpriseFeedDoneFileName);
156 if (!enterpriseFeedDoneFile.exists()){
157 try {
158 enterpriseFeedDoneFile.createNewFile();
159 } catch (IOException e) {
160 LOG.error("Unable to create done file for enterprise feed output group.", e);
161 throw new RuntimeException("Unable to create done file for enterprise feed output group.", e);
162 }
163 }
164
165 }
166 }
167
168 /**
169 * Sets the laborOriginEntryDirectoryName attribute value.
170 * @param laborOriginEntryDirectoryName The laborOriginEntryDirectoryName to set.
171 */
172 public void setLaborOriginEntryDirectoryName(String laborOriginEntryDirectoryName) {
173 this.laborOriginEntryDirectoryName = laborOriginEntryDirectoryName;
174 }
175
176 /**
177 * Reorders the files in case there's a dependency on the order in which files are fed upon. For this implementation, the
178 * purpose is to always order files in a way such that unit testing will be predictable.
179 *
180 * @param doneFiles
181 */
182 protected void reorderDoneFiles(File[] doneFiles) {
183 // sort the list so that the unit tests will have more predictable results
184 Arrays.sort(doneFiles);
185 }
186
187 /**
188 * Given the doneFile, this method finds the data file corresponding to the done file
189 *
190 * @param doneFile
191 * @return a File for the data file, or null if the file doesn't exist or is not readable
192 */
193 protected File getDataFile(File doneFile) {
194 String doneFileAbsPath = doneFile.getAbsolutePath();
195 if (!doneFileAbsPath.endsWith(DONE_FILE_SUFFIX)) {
196 LOG.error("Done file name must end with " + DONE_FILE_SUFFIX);
197 throw new IllegalArgumentException("Done file name must end with " + DONE_FILE_SUFFIX);
198 }
199 String dataFileAbsPath = StringUtils.removeEnd(doneFileAbsPath, DONE_FILE_SUFFIX) + DATA_FILE_SUFFIX;
200 File dataFile = new File(dataFileAbsPath);
201 if (!dataFile.exists() || !dataFile.canRead()) {
202 LOG.error("Cannot find/read data file " + dataFileAbsPath);
203 return null;
204 }
205 return dataFile;
206 }
207
208 /**
209 * Given the doneFile, this method finds the reconciliation file corresponding to the data file
210 *
211 * @param doneFile
212 * @return a file for the reconciliation data, or null if the file doesn't exist or is not readable
213 */
214 protected File getReconFile(File doneFile) {
215 String doneFileAbsPath = doneFile.getAbsolutePath();
216 if (!doneFileAbsPath.endsWith(DONE_FILE_SUFFIX)) {
217 LOG.error("Done file name must end with " + DONE_FILE_SUFFIX);
218 throw new IllegalArgumentException("DOne file name must end with " + DONE_FILE_SUFFIX);
219 }
220 String reconFileAbsPath = StringUtils.removeEnd(doneFileAbsPath, DONE_FILE_SUFFIX) + RECON_FILE_SUFFIX;
221 File reconFile = new File(reconFileAbsPath);
222 if (!reconFile.exists() || !reconFile.canRead()) {
223 LOG.error("Cannot find/read data file " + reconFileAbsPath);
224 return null;
225 }
226 return reconFile;
227 }
228
229 /**
230 * Gets the directoryName attribute.
231 *
232 * @return Returns the directoryName.
233 */
234 public String getDirectoryName() {
235 return directoryName;
236 }
237
238 /**
239 * Sets the directoryName attribute value.
240 *
241 * @param directoryName The directoryName to set.
242 */
243 public void setDirectoryName(String directoryName) {
244 this.directoryName = directoryName;
245 }
246
247 /**
248 * Gets the originEntryGroupService attribute.
249 *
250 * @return Returns the originEntryGroupService.
251 */
252 public OriginEntryGroupService getOriginEntryGroupService() {
253 return originEntryGroupService;
254 }
255
256 /**
257 * Sets the originEntryGroupService attribute value.
258 *
259 * @param originEntryGroupService The originEntryGroupService to set.
260 */
261 public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) {
262 this.originEntryGroupService = originEntryGroupService;
263 }
264
265 /**
266 * Gets the dateTimeService attribute.
267 *
268 * @return Returns the dateTimeService.
269 */
270 public DateTimeService getDateTimeService() {
271 return dateTimeService;
272 }
273
274 /**
275 * Sets the dateTimeService attribute value.
276 *
277 * @param dateTimeService The dateTimeService to set.
278 */
279 public void setDateTimeService(DateTimeService dateTimeService) {
280 this.dateTimeService = dateTimeService;
281 }
282
283 /**
284 * Gets the fileEnterpriseFeederHelperService attribute.
285 *
286 * @return Returns the fileEnterpriseFeederHelperService.
287 */
288 public FileEnterpriseFeederHelperService getFileEnterpriseFeederHelperService() {
289 return fileEnterpriseFeederHelperService;
290 }
291
292 /**
293 * Sets the fileEnterpriseFeederHelperService attribute value.
294 *
295 * @param fileEnterpriseFeederHelperService The fileEnterpriseFeederHelperService to set.
296 */
297 public void setFileEnterpriseFeederHelperService(FileEnterpriseFeederHelperService fileEnterpriseFeederHelperServiceImpl) {
298 this.fileEnterpriseFeederHelperService = fileEnterpriseFeederHelperServiceImpl;
299 }
300
301 /**
302 * Gets the enterpriseFeederNotificationService attribute.
303 *
304 * @return Returns the enterpriseFeederNotificationService.
305 */
306 public EnterpriseFeederNotificationService getEnterpriseFeederNotificationService() {
307 return enterpriseFeederNotificationService;
308 }
309
310 /**
311 * Sets the enterpriseFeederNotificationService attribute value.
312 *
313 * @param enterpriseFeederNotificationService The enterpriseFeederNotificationService to set.
314 */
315 public void setEnterpriseFeederNotificationService(EnterpriseFeederNotificationService enterpriseFeederNotificationService) {
316 this.enterpriseFeederNotificationService = enterpriseFeederNotificationService;
317 }
318
319 /**
320 * Gets the reconciliationTableId attribute.
321 *
322 * @return Returns the reconciliationTableId.
323 */
324 public String getReconciliationTableId() {
325 return reconciliationTableId;
326 }
327
328 /**
329 * Sets the reconciliationTableId attribute value.
330 *
331 * @param reconciliationTableId The reconciliationTableId to set.
332 */
333 public void setReconciliationTableId(String reconciliationTableId) {
334 this.reconciliationTableId = reconciliationTableId;
335 }
336
337 protected void generateReport(List<EnterpriseFeederStatusAndErrorMessagesWrapper> statusAndErrorsList, LedgerSummaryReport report, String outputFileName) {
338 reportWriterService.writeFormattedMessageLine("Output File Name: %s", outputFileName);
339 reportWriterService.writeNewLines(1);
340 generateFilesLoadedStatusReport(statusAndErrorsList);
341 reportWriterService.pageBreak();
342 report.writeReport(reportWriterService);
343 }
344
345 protected void generateFilesLoadedStatusReport(List<EnterpriseFeederStatusAndErrorMessagesWrapper> statusAndErrorsList) {
346 boolean successfulFileLoaded = false;
347 reportWriterService.writeSubTitle("Files Successfully Loaded");
348 for (EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors : statusAndErrorsList) {
349 if (!statusAndErrors.getStatus().isErrorEvent()) {
350 reportWriterService.writeFormattedMessageLine("Data file: %s", statusAndErrors.getDataFileName());
351 reportWriterService.writeFormattedMessageLine("Reconciliation file: %s", statusAndErrors.getReconFileName());
352 reportWriterService.writeFormattedMessageLine("Status: %s", statusAndErrors.getStatus().getStatusDescription());
353 reportWriterService.writeNewLines(1);
354
355 successfulFileLoaded = true;
356 }
357 }
358 if (!successfulFileLoaded) {
359 reportWriterService.writeFormattedMessageLine("No files were successfully loaded");
360 }
361
362 reportWriterService.writeNewLines(2);
363
364 boolean unsuccessfulFileLoaded = false;
365 reportWriterService.writeSubTitle("Files NOT Successfully Loaded");
366 for (EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors : statusAndErrorsList) {
367 if (statusAndErrors.getStatus().isErrorEvent()) {
368 reportWriterService.writeFormattedMessageLine("Data file: %s", statusAndErrors.getDataFileName() == null ? "" : statusAndErrors.getDataFileName());
369 reportWriterService.writeFormattedMessageLine("Reconciliation file: %s", statusAndErrors.getReconFileName() == null ? "" : statusAndErrors.getReconFileName());
370 reportWriterService.writeFormattedMessageLine("Status: %s", statusAndErrors.getStatus().getStatusDescription());
371 reportWriterService.writeNewLines(1);
372
373 unsuccessfulFileLoaded = true;
374 }
375 }
376 if (!unsuccessfulFileLoaded) {
377 reportWriterService.writeFormattedMessageLine("All files were successfully loaded");
378 }
379
380 }
381
382 /**
383 * Sets the reportWriterService attribute value.
384 * @param reportWriterService The reportWriterService to set.
385 */
386 public void setReportWriterService(ReportWriterService reportWriterService) {
387 this.reportWriterService = reportWriterService;
388 }
389 }