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 }