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.File;
019: import java.io.FileFilter;
020: import java.util.ArrayList;
021: import java.util.Arrays;
022:
023: import org.apache.commons.io.filefilter.SuffixFileFilter;
024: import org.apache.commons.lang.StringUtils;
025: import org.kuali.core.service.DateTimeService;
026: import org.kuali.module.gl.bo.OriginEntryGroup;
027: import org.kuali.module.gl.bo.OriginEntrySource;
028: import org.kuali.module.gl.service.EnterpriseFeederNotificationService;
029: import org.kuali.module.gl.service.EnterpriseFeederService;
030: import org.kuali.module.gl.service.FileEnterpriseFeederHelperService;
031: import org.kuali.module.gl.service.OriginEntryGroupService;
032: import org.kuali.module.gl.util.EnterpriseFeederStatusAndErrorMessagesWrapper;
033: import org.kuali.module.gl.util.Message;
034: import org.kuali.module.gl.util.RequiredFilesMissingStatus;
035:
036: /**
037: * This class iterates through the files in the enterprise feeder staging directory, which is injected by Spring. Note: this class
038: * is NOT annotated as transactional. This allows the helper service, which is defined as transactional, to do a per-file
039: * transaction.
040: */
041: public class FileEnterpriseFeederServiceImpl implements
042: EnterpriseFeederService {
043: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
044: .getLogger(FileEnterpriseFeederServiceImpl.class);
045:
046: private String directoryName;
047:
048: private OriginEntryGroupService originEntryGroupService;
049: private DateTimeService dateTimeService;
050: private FileEnterpriseFeederHelperService fileEnterpriseFeederHelperService;
051: private EnterpriseFeederNotificationService enterpriseFeederNotificationService;
052: private String reconciliationTableId;
053:
054: /**
055: * Feeds file sets in the directory whose name is returned by the invocation to getDirectoryName()
056: *
057: * @see org.kuali.module.gl.service.EnterpriseFeederService#feed(java.lang.String)
058: */
059: public void feed(String processName, boolean performNotifications) {
060: // ensure that this feeder implementation may not be run concurrently on this JVM
061:
062: // to consider: maybe use java NIO classes to perform done file locking?
063: synchronized (FileEnterpriseFeederServiceImpl.class) {
064: if (StringUtils.isBlank(directoryName)) {
065: throw new IllegalArgumentException(
066: "directoryName not set for FileEnterpriseFeederServiceImpl.");
067: }
068: FileFilter doneFileFilter = new SuffixFileFilter(
069: DONE_FILE_SUFFIX);
070:
071: OriginEntryGroup originEntryGroup = createNewGroupForFeed(OriginEntrySource.ENTERPRISE_FEED);
072: LOG
073: .info("New group ID created for enterprise feeder service run: "
074: + originEntryGroup.getId());
075:
076: File directory = new File(directoryName);
077: if (!directory.exists() || !directory.isDirectory()) {
078: throw new RuntimeException(
079: "Directory doesn't exist and or it's not really a directory "
080: + directoryName);
081: }
082:
083: File[] doneFiles = directory.listFiles(doneFileFilter);
084: reorderDoneFiles(doneFiles);
085:
086: for (File doneFile : doneFiles) {
087: File dataFile = null;
088: File reconFile = null;
089:
090: EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors = new EnterpriseFeederStatusAndErrorMessagesWrapper();
091: statusAndErrors
092: .setErrorMessages(new ArrayList<Message>());
093:
094: try {
095: dataFile = getDataFile(doneFile);
096: reconFile = getReconFile(doneFile);
097:
098: if (dataFile == null) {
099: LOG
100: .error("Unable to find data file for done file: "
101: + doneFile.getAbsolutePath());
102: statusAndErrors
103: .getErrorMessages()
104: .add(
105: new Message(
106: "Unable to find data file for done file: "
107: + doneFile
108: .getAbsolutePath(),
109: Message.TYPE_FATAL));
110: statusAndErrors
111: .setStatus(new RequiredFilesMissingStatus());
112: }
113: if (reconFile == null) {
114: LOG
115: .error("Unable to find recon file for done file: "
116: + doneFile.getAbsolutePath());
117: statusAndErrors
118: .getErrorMessages()
119: .add(
120: new Message(
121: "Unable to find recon file for done file: "
122: + doneFile
123: .getAbsolutePath(),
124: Message.TYPE_FATAL));
125: statusAndErrors
126: .setStatus(new RequiredFilesMissingStatus());
127: }
128: if (dataFile != null && reconFile != null) {
129: LOG.info("Data file: "
130: + dataFile.getAbsolutePath());
131: LOG.info("Reconciliation File: "
132: + reconFile.getAbsolutePath());
133:
134: fileEnterpriseFeederHelperService.feedOnFile(
135: doneFile, dataFile, reconFile,
136: originEntryGroup, processName,
137: reconciliationTableId, statusAndErrors);
138: }
139: } catch (RuntimeException e) {
140: // we need to be extremely resistant to a file load failing so that it doesn't prevent other files from loading
141: LOG
142: .error("Caught exception when feeding done file: "
143: + doneFile.getAbsolutePath());
144: } finally {
145: boolean doneFileDeleted = doneFile.delete();
146: if (!doneFileDeleted) {
147: statusAndErrors
148: .getErrorMessages()
149: .add(
150: new Message(
151: "Unable to delete done file: "
152: + doneFile
153: .getAbsolutePath(),
154: Message.TYPE_FATAL));
155: }
156: if (performNotifications) {
157: enterpriseFeederNotificationService
158: .notifyFileFeedStatus(processName,
159: statusAndErrors.getStatus(),
160: doneFile, dataFile, reconFile,
161: statusAndErrors
162: .getErrorMessages());
163: }
164: }
165: }
166:
167: markGroupReady(originEntryGroup);
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: throw new IllegalArgumentException(
192: "DOne file name must end with " + DONE_FILE_SUFFIX);
193: }
194: String dataFileAbsPath = StringUtils.removeEnd(doneFileAbsPath,
195: DONE_FILE_SUFFIX)
196: + DATA_FILE_SUFFIX;
197: File dataFile = new File(dataFileAbsPath);
198: if (!dataFile.exists() || !dataFile.canRead()) {
199: LOG.error("Cannot find/read data file " + dataFileAbsPath);
200: return null;
201: }
202: return dataFile;
203: }
204:
205: /**
206: * Given the doneFile, this method finds the reconciliation file corresponding to the data file
207: *
208: * @param doneFile
209: * @return a file for the reconciliation data, or null if the file doesn't exist or is not readable
210: */
211: protected File getReconFile(File doneFile) {
212: String doneFileAbsPath = doneFile.getAbsolutePath();
213: if (!doneFileAbsPath.endsWith(DONE_FILE_SUFFIX)) {
214: throw new IllegalArgumentException(
215: "DOne file name must end with " + DONE_FILE_SUFFIX);
216: }
217: String reconFileAbsPath = StringUtils.removeEnd(
218: doneFileAbsPath, DONE_FILE_SUFFIX)
219: + RECON_FILE_SUFFIX;
220: File reconFile = new File(reconFileAbsPath);
221: if (!reconFile.exists() || !reconFile.canRead()) {
222: LOG.error("Cannot find/read data file " + reconFileAbsPath);
223: return null;
224: }
225: return reconFile;
226: }
227:
228: /**
229: * Creates a new origin entry group to which the origin entries in the files will be added
230: *
231: * @param groupSourceCode the origin entry group for the entries from the enterprise feed
232: * @param valid
233: * @param process
234: * @param scrub
235: * @return
236: */
237: protected OriginEntryGroup createNewGroupForFeed(
238: String groupSourceCode) {
239: return originEntryGroupService.createGroup(dateTimeService
240: .getCurrentSqlDate(), groupSourceCode, true, false,
241: true);
242: }
243:
244: /**
245: * This method marks that an origin entry group
246: *
247: * @param originEntryGroup
248: */
249: protected void markGroupReady(OriginEntryGroup originEntryGroup) {
250: originEntryGroup.setProcess(true);
251: originEntryGroupService.save(originEntryGroup);
252: }
253:
254: /**
255: * Gets the directoryName attribute.
256: *
257: * @return Returns the directoryName.
258: */
259: public String getDirectoryName() {
260: return directoryName;
261: }
262:
263: /**
264: * Sets the directoryName attribute value.
265: *
266: * @param directoryName The directoryName to set.
267: */
268: public void setDirectoryName(String directoryName) {
269: this .directoryName = directoryName;
270: }
271:
272: /**
273: * Gets the originEntryGroupService attribute.
274: *
275: * @return Returns the originEntryGroupService.
276: */
277: public OriginEntryGroupService getOriginEntryGroupService() {
278: return originEntryGroupService;
279: }
280:
281: /**
282: * Sets the originEntryGroupService attribute value.
283: *
284: * @param originEntryGroupService The originEntryGroupService to set.
285: */
286: public void setOriginEntryGroupService(
287: OriginEntryGroupService originEntryGroupService) {
288: this .originEntryGroupService = originEntryGroupService;
289: }
290:
291: /**
292: * Gets the dateTimeService attribute.
293: *
294: * @return Returns the dateTimeService.
295: */
296: public DateTimeService getDateTimeService() {
297: return dateTimeService;
298: }
299:
300: /**
301: * Sets the dateTimeService attribute value.
302: *
303: * @param dateTimeService The dateTimeService to set.
304: */
305: public void setDateTimeService(DateTimeService dateTimeService) {
306: this .dateTimeService = dateTimeService;
307: }
308:
309: /**
310: * Gets the fileEnterpriseFeederHelperService attribute.
311: *
312: * @return Returns the fileEnterpriseFeederHelperService.
313: */
314: public FileEnterpriseFeederHelperService getFileEnterpriseFeederHelperService() {
315: return fileEnterpriseFeederHelperService;
316: }
317:
318: /**
319: * Sets the fileEnterpriseFeederHelperService attribute value.
320: *
321: * @param fileEnterpriseFeederHelperService The fileEnterpriseFeederHelperService to set.
322: */
323: public void setFileEnterpriseFeederHelperService(
324: FileEnterpriseFeederHelperService fileEnterpriseFeederHelperServiceImpl) {
325: this .fileEnterpriseFeederHelperService = fileEnterpriseFeederHelperServiceImpl;
326: }
327:
328: /**
329: * Gets the enterpriseFeederNotificationService attribute.
330: *
331: * @return Returns the enterpriseFeederNotificationService.
332: */
333: public EnterpriseFeederNotificationService getEnterpriseFeederNotificationService() {
334: return enterpriseFeederNotificationService;
335: }
336:
337: /**
338: * Sets the enterpriseFeederNotificationService attribute value.
339: *
340: * @param enterpriseFeederNotificationService The enterpriseFeederNotificationService to set.
341: */
342: public void setEnterpriseFeederNotificationService(
343: EnterpriseFeederNotificationService enterpriseFeederNotificationService) {
344: this .enterpriseFeederNotificationService = enterpriseFeederNotificationService;
345: }
346:
347: /**
348: * Gets the reconciliationTableId attribute.
349: *
350: * @return Returns the reconciliationTableId.
351: */
352: public String getReconciliationTableId() {
353: return reconciliationTableId;
354: }
355:
356: /**
357: * Sets the reconciliationTableId attribute value.
358: *
359: * @param reconciliationTableId The reconciliationTableId to set.
360: */
361: public void setReconciliationTableId(String reconciliationTableId) {
362: this.reconciliationTableId = reconciliationTableId;
363: }
364: }
|