View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.gl.batch.service.impl;
17  
18  import java.io.File;
19  import java.io.FileFilter;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.PrintStream;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.apache.commons.io.filefilter.SuffixFileFilter;
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.ole.gl.GeneralLedgerConstants;
30  import org.kuali.ole.gl.batch.service.EnterpriseFeederNotificationService;
31  import org.kuali.ole.gl.batch.service.EnterpriseFeederService;
32  import org.kuali.ole.gl.batch.service.FileEnterpriseFeederHelperService;
33  import org.kuali.ole.gl.report.LedgerSummaryReport;
34  import org.kuali.ole.gl.service.OriginEntryGroupService;
35  import org.kuali.ole.gl.service.impl.EnterpriseFeederStatusAndErrorMessagesWrapper;
36  import org.kuali.ole.sys.Message;
37  import org.kuali.ole.sys.batch.InitiateDirectoryBase;
38  import org.kuali.ole.sys.service.ReportWriterService;
39  import org.kuali.rice.core.api.datetime.DateTimeService;
40  
41  /**
42   * This class iterates through the files in the enterprise feeder staging directory, which is injected by Spring. Note: this class
43   * is NOT annotated as transactional. This allows the helper service, which is defined as transactional, to do a per-file
44   * transaction.
45   */
46  public class FileEnterpriseFeederServiceImpl extends InitiateDirectoryBase implements EnterpriseFeederService {
47      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FileEnterpriseFeederServiceImpl.class);
48  
49      private String directoryName;
50      private String glOriginEntryDirectoryName;
51  
52      private OriginEntryGroupService originEntryGroupService;
53      private DateTimeService dateTimeService;
54      private FileEnterpriseFeederHelperService fileEnterpriseFeederHelperService;
55      private EnterpriseFeederNotificationService enterpriseFeederNotificationService;
56      private String reconciliationTableId;
57  
58      private ReportWriterService reportWriterService;
59      
60      /**
61       * Feeds file sets in the directory whose name is returned by the invocation to getDirectoryName()
62       * 
63       * @see org.kuali.ole.gl.batch.service.EnterpriseFeederService#feed(java.lang.String)
64       */
65      public void feed(String processName, boolean performNotifications) {
66          // ensure that this feeder implementation may not be run concurrently on this JVM
67  
68          // to consider: maybe use java NIO classes to perform done file locking?
69          synchronized (FileEnterpriseFeederServiceImpl.class) {
70              if (StringUtils.isBlank(directoryName)) {
71                  throw new IllegalArgumentException("directoryName not set for FileEnterpriseFeederServiceImpl.");
72              }
73              
74              //add a step to check for directory paths
75              prepareDirectories(getRequiredDirectoryNames());
76              
77              FileFilter doneFileFilter = new SuffixFileFilter(DONE_FILE_SUFFIX);
78  
79              File enterpriseFeedFile = null;
80              String enterpriseFeedFileName = GeneralLedgerConstants.BatchFileSystem.ENTERPRISE_FEED + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
81              enterpriseFeedFile = new File(glOriginEntryDirectoryName + File.separator + enterpriseFeedFileName);
82              
83              PrintStream enterpriseFeedPs = null;
84              try {
85                  enterpriseFeedPs = new PrintStream(enterpriseFeedFile);
86              } catch (FileNotFoundException e) {
87                  LOG.error("enterpriseFeedFile doesn't exist " + enterpriseFeedFileName);
88                  throw new RuntimeException("enterpriseFeedFile doesn't exist " + enterpriseFeedFileName);
89              }
90              
91              LOG.info("New File created for enterprise feeder service run: " + enterpriseFeedFileName);
92  
93              File directory = new File(directoryName);
94              if (!directory.exists() || !directory.isDirectory()) {
95                  LOG.error("Directory doesn't exist and or it's not really a directory " + directoryName);
96                  throw new RuntimeException("Directory doesn't exist and or it's not really a directory " + directoryName);
97              }
98  
99              File[] doneFiles = directory.listFiles(doneFileFilter);
100             reorderDoneFiles(doneFiles);
101 
102             LedgerSummaryReport ledgerSummaryReport = new LedgerSummaryReport();
103 
104             List<EnterpriseFeederStatusAndErrorMessagesWrapper> statusAndErrorsList = new ArrayList<EnterpriseFeederStatusAndErrorMessagesWrapper>();
105             
106             for (File doneFile : doneFiles) {
107                 File dataFile = null;
108                 File reconFile = null;
109                 
110 
111                 EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors = new EnterpriseFeederStatusAndErrorMessagesWrapper();
112                 statusAndErrors.setErrorMessages(new ArrayList<Message>());
113 
114                 try {                
115                     dataFile = getDataFile(doneFile);
116                     reconFile = getReconFile(doneFile);
117 
118                     statusAndErrors.setFileNames(dataFile, reconFile, doneFile);
119 
120                     if (dataFile == null) {
121                         LOG.error("Unable to find data file for done file: " + doneFile.getAbsolutePath());
122                         statusAndErrors.getErrorMessages().add(new Message("Unable to find data file for done file: " + doneFile.getAbsolutePath(), Message.TYPE_FATAL));
123                         statusAndErrors.setStatus(new RequiredFilesMissingStatus());
124                     }
125                     if (reconFile == null) {
126                         LOG.error("Unable to find recon file for done file: " + doneFile.getAbsolutePath());
127                         statusAndErrors.getErrorMessages().add(new Message("Unable to find recon file for done file: " + doneFile.getAbsolutePath(), Message.TYPE_FATAL));
128                         statusAndErrors.setStatus(new RequiredFilesMissingStatus());
129                     }
130 
131                     if (dataFile != null && reconFile != null) {
132                         LOG.info("Data file: " + dataFile.getAbsolutePath());
133                         LOG.info("Reconciliation File: " + reconFile.getAbsolutePath());
134 
135                         fileEnterpriseFeederHelperService.feedOnFile(doneFile, dataFile, reconFile, enterpriseFeedPs, processName, reconciliationTableId, statusAndErrors, ledgerSummaryReport);
136                     }
137                 }
138                 catch (RuntimeException e) {
139                     // we need to be extremely resistant to a file load failing so that it doesn't prevent other files from loading
140                     LOG.error("Caught exception when feeding done file: " + doneFile.getAbsolutePath());
141                 }
142                 finally {
143                     statusAndErrorsList.add(statusAndErrors);
144                     boolean doneFileDeleted = doneFile.delete();
145                     if (!doneFileDeleted) {
146                         statusAndErrors.getErrorMessages().add(new Message("Unable to delete done file: " + doneFile.getAbsolutePath(), Message.TYPE_FATAL));
147                     }
148                     if (performNotifications) {
149                         enterpriseFeederNotificationService.notifyFileFeedStatus(processName, statusAndErrors.getStatus(), doneFile, dataFile, reconFile, statusAndErrors.getErrorMessages());
150                     }
151                 }
152             }
153             
154             enterpriseFeedPs.close();
155             generateReport(statusAndErrorsList, ledgerSummaryReport, glOriginEntryDirectoryName + File.separator + enterpriseFeedFileName);
156             
157             String enterpriseFeedDoneFileName = enterpriseFeedFileName.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION);
158             File enterpriseFeedDoneFile = new File (glOriginEntryDirectoryName + File.separator + enterpriseFeedDoneFileName);
159             if (!enterpriseFeedDoneFile.exists()){
160                 try {
161                     enterpriseFeedDoneFile.createNewFile();
162                 } catch (IOException e) {
163                     LOG.error("Unable to create done file for enterprise feed output group.", e);
164                     throw new RuntimeException("Unable to create done file for enterprise feed output group.", e);
165                 }
166             }
167             
168         }
169     }
170 
171     /**
172      * Reorders the files in case there's a dependency on the order in which files are fed upon. For this implementation, the
173      * purpose is to always order files in a way such that unit testing will be predictable.
174      * 
175      * @param doneFiles
176      */
177     protected void reorderDoneFiles(File[] doneFiles) {
178         // sort the list so that the unit tests will have more predictable results
179         Arrays.sort(doneFiles);
180     }
181 
182     /**
183      * Given the doneFile, this method finds the data file corresponding to the done file
184      * 
185      * @param doneFile
186      * @return a File for the data file, or null if the file doesn't exist or is not readable
187      */
188     protected File getDataFile(File doneFile) {
189         String doneFileAbsPath = doneFile.getAbsolutePath();
190         if (!doneFileAbsPath.endsWith(DONE_FILE_SUFFIX)) {
191             LOG.error("Done file name must end with " + DONE_FILE_SUFFIX);
192             throw new IllegalArgumentException("Done file name must end with " + DONE_FILE_SUFFIX);
193         }
194         String dataFileAbsPath = StringUtils.removeEnd(doneFileAbsPath, DONE_FILE_SUFFIX) + DATA_FILE_SUFFIX;
195         File dataFile = new File(dataFileAbsPath);
196         if (!dataFile.exists() || !dataFile.canRead()) {
197             LOG.error("Cannot find/read data file " + dataFileAbsPath);
198             return null;
199         }
200         return dataFile;
201     }
202 
203     /**
204      * Given the doneFile, this method finds the reconciliation file corresponding to the data file
205      * 
206      * @param doneFile
207      * @return a file for the reconciliation data, or null if the file doesn't exist or is not readable
208      */
209     protected File getReconFile(File doneFile) {
210         String doneFileAbsPath = doneFile.getAbsolutePath();
211         if (!doneFileAbsPath.endsWith(DONE_FILE_SUFFIX)) {
212             LOG.error("Done file name must end with " + DONE_FILE_SUFFIX);
213             throw new IllegalArgumentException("DOne file name must end with " + DONE_FILE_SUFFIX);
214         }
215         String reconFileAbsPath = StringUtils.removeEnd(doneFileAbsPath, DONE_FILE_SUFFIX) + RECON_FILE_SUFFIX;
216         File reconFile = new File(reconFileAbsPath);
217         if (!reconFile.exists() || !reconFile.canRead()) {
218             LOG.error("Cannot find/read data file " + reconFileAbsPath);
219             return null;
220         }
221         return reconFile;
222     }
223 
224     /**
225      * Gets the directoryName attribute.
226      * 
227      * @return Returns the directoryName.
228      */
229     public String getDirectoryName() {
230         return directoryName;
231     }
232 
233     /**
234      * Sets the directoryName attribute value.
235      * 
236      * @param directoryName The directoryName to set.
237      */
238     public void setDirectoryName(String directoryName) {
239         this.directoryName = directoryName;
240     }
241 
242     /**
243      * Gets the originEntryGroupService attribute.
244      * 
245      * @return Returns the originEntryGroupService.
246      */
247     public OriginEntryGroupService getOriginEntryGroupService() {
248         return originEntryGroupService;
249     }
250 
251     /**
252      * Sets the originEntryGroupService attribute value.
253      * 
254      * @param originEntryGroupService The originEntryGroupService to set.
255      */
256     public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) {
257         this.originEntryGroupService = originEntryGroupService;
258     }
259 
260     /**
261      * Gets the dateTimeService attribute.
262      * 
263      * @return Returns the dateTimeService.
264      */
265     public DateTimeService getDateTimeService() {
266         return dateTimeService;
267     }
268 
269     /**
270      * Sets the dateTimeService attribute value.
271      * 
272      * @param dateTimeService The dateTimeService to set.
273      */
274     public void setDateTimeService(DateTimeService dateTimeService) {
275         this.dateTimeService = dateTimeService;
276     }
277 
278     /**
279      * Gets the fileEnterpriseFeederHelperService attribute.
280      * 
281      * @return Returns the fileEnterpriseFeederHelperService.
282      */
283     public FileEnterpriseFeederHelperService getFileEnterpriseFeederHelperService() {
284         return fileEnterpriseFeederHelperService;
285     }
286 
287     /**
288      * Sets the fileEnterpriseFeederHelperService attribute value.
289      * 
290      * @param fileEnterpriseFeederHelperService The fileEnterpriseFeederHelperService to set.
291      */
292     public void setFileEnterpriseFeederHelperService(FileEnterpriseFeederHelperService fileEnterpriseFeederHelperServiceImpl) {
293         this.fileEnterpriseFeederHelperService = fileEnterpriseFeederHelperServiceImpl;
294     }
295 
296     /**
297      * Gets the enterpriseFeederNotificationService attribute.
298      * 
299      * @return Returns the enterpriseFeederNotificationService.
300      */
301     public EnterpriseFeederNotificationService getEnterpriseFeederNotificationService() {
302         return enterpriseFeederNotificationService;
303     }
304 
305     /**
306      * Sets the enterpriseFeederNotificationService attribute value.
307      * 
308      * @param enterpriseFeederNotificationService The enterpriseFeederNotificationService to set.
309      */
310     public void setEnterpriseFeederNotificationService(EnterpriseFeederNotificationService enterpriseFeederNotificationService) {
311         this.enterpriseFeederNotificationService = enterpriseFeederNotificationService;
312     }
313 
314     /**
315      * Gets the reconciliationTableId attribute.
316      * 
317      * @return Returns the reconciliationTableId.
318      */
319     public String getReconciliationTableId() {
320         return reconciliationTableId;
321     }
322 
323     /**
324      * Sets the reconciliationTableId attribute value.
325      * 
326      * @param reconciliationTableId The reconciliationTableId to set.
327      */
328     public void setReconciliationTableId(String reconciliationTableId) {
329         this.reconciliationTableId = reconciliationTableId;
330     }
331 
332     public void setGlOriginEntryDirectoryName(String glOriginEntryDirectoryName) {
333         this.glOriginEntryDirectoryName = glOriginEntryDirectoryName;
334     }
335     
336     protected void generateReport(List<EnterpriseFeederStatusAndErrorMessagesWrapper> statusAndErrorsList, LedgerSummaryReport report, String outputFileName) {
337         reportWriterService.writeFormattedMessageLine("Output File Name:        %s", outputFileName);
338         reportWriterService.writeNewLines(1);
339         generateFilesLoadedStatusReport(statusAndErrorsList);
340         reportWriterService.pageBreak();
341         report.writeReport(reportWriterService);
342     }
343     
344     protected void generateFilesLoadedStatusReport(List<EnterpriseFeederStatusAndErrorMessagesWrapper> statusAndErrorsList) {
345         boolean successfulFileLoaded = false;
346         reportWriterService.writeSubTitle("Files Successfully Loaded");
347         for (EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors : statusAndErrorsList) {
348             if (!statusAndErrors.getStatus().isErrorEvent()) {
349                 reportWriterService.writeFormattedMessageLine("Data file:               %s", statusAndErrors.getDataFileName());
350                 reportWriterService.writeFormattedMessageLine("Reconciliation file:     %s", statusAndErrors.getReconFileName());
351                 reportWriterService.writeFormattedMessageLine("Status:                  %s", statusAndErrors.getStatus().getStatusDescription());
352                 reportWriterService.writeNewLines(1);
353                 
354                 successfulFileLoaded = true;
355             }
356         }
357         if (!successfulFileLoaded) {
358             reportWriterService.writeFormattedMessageLine("No files were successfully loaded");
359         }
360         
361         reportWriterService.writeNewLines(2);
362         
363         boolean unsuccessfulFileLoaded = false;
364         reportWriterService.writeSubTitle("Files NOT Successfully Loaded");
365         for (EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors : statusAndErrorsList) {
366             if (statusAndErrors.getStatus().isErrorEvent()) {
367                 reportWriterService.writeFormattedMessageLine("Data file:               %s", statusAndErrors.getDataFileName() == null ? "" : statusAndErrors.getDataFileName());
368                 reportWriterService.writeFormattedMessageLine("Reconciliation file:     %s", statusAndErrors.getReconFileName() == null ? "" : statusAndErrors.getReconFileName());
369                 reportWriterService.writeFormattedMessageLine("Status:                  %s", statusAndErrors.getStatus().getStatusDescription());
370                 reportWriterService.writeNewLines(1);
371                 
372                 unsuccessfulFileLoaded = true;
373             }
374         }
375         if (!unsuccessfulFileLoaded) {
376             reportWriterService.writeFormattedMessageLine("All files were successfully loaded");
377         }
378         
379     }
380 
381     /**
382      * Sets the reportWriterService attribute value.
383      * @param reportWriterService The reportWriterService to set.
384      */
385     public void setReportWriterService(ReportWriterService reportWriterService) {
386         this.reportWriterService = reportWriterService;
387     }
388 
389     /**
390      * @see org.kuali.ole.sys.batch.service.impl.InitiateDirectoryImpl#getRequiredDirectoryNames()
391      */
392     @Override
393     public List<String> getRequiredDirectoryNames() {
394         return new ArrayList<String>() {{add(getDirectoryName()); }};
395     }
396 }