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.pdp.service.impl;
017
018 import java.io.File;
019 import java.io.FileInputStream;
020 import java.io.FileNotFoundException;
021 import java.io.FileOutputStream;
022 import java.io.IOException;
023 import java.io.PrintStream;
024 import java.sql.Timestamp;
025 import java.text.MessageFormat;
026 import java.util.Calendar;
027 import java.util.List;
028
029 import org.apache.commons.io.IOUtils;
030 import org.apache.commons.lang.StringUtils;
031 import org.kuali.kfs.pdp.businessobject.Batch;
032 import org.kuali.kfs.pdp.businessobject.CustomerProfile;
033 import org.kuali.kfs.pdp.businessobject.LoadPaymentStatus;
034 import org.kuali.kfs.pdp.businessobject.PaymentFileLoad;
035 import org.kuali.kfs.pdp.businessobject.PaymentGroup;
036 import org.kuali.kfs.pdp.service.CustomerProfileService;
037 import org.kuali.kfs.pdp.service.PaymentFileService;
038 import org.kuali.kfs.pdp.service.PaymentFileValidationService;
039 import org.kuali.kfs.pdp.service.PdpEmailService;
040 import org.kuali.kfs.sys.KFSConstants;
041 import org.kuali.kfs.sys.KFSKeyConstants;
042 import org.kuali.kfs.sys.batch.BatchInputFileType;
043 import org.kuali.kfs.sys.batch.service.BatchInputFileService;
044 import org.kuali.kfs.sys.exception.ParseException;
045 import org.kuali.rice.kns.service.BusinessObjectService;
046 import org.kuali.rice.kns.service.DateTimeService;
047 import org.kuali.rice.kns.service.KualiConfigurationService;
048 import org.kuali.rice.kns.service.ParameterService;
049 import org.kuali.rice.kns.util.ErrorMap;
050 import org.kuali.rice.kns.util.ErrorMessage;
051 import org.kuali.rice.kns.util.GlobalVariables;
052 import org.kuali.rice.kns.util.KualiInteger;
053 import org.kuali.rice.kns.util.MessageMap;
054 import org.springframework.transaction.annotation.Transactional;
055
056 /**
057 * @see org.kuali.kfs.pdp.service.PaymentFileService
058 */
059 @Transactional
060 public class PaymentFileServiceImpl implements PaymentFileService {
061 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentFileServiceImpl.class);
062
063 private String outgoingDirectoryName;
064
065 private ParameterService parameterService;
066 private CustomerProfileService customerProfileService;
067 private BatchInputFileService batchInputFileService;
068 private PaymentFileValidationService paymentFileValidationService;
069 private BusinessObjectService businessObjectService;
070 private DateTimeService dateTimeService;
071 private PdpEmailService paymentFileEmailService;
072 private KualiConfigurationService kualiConfigurationService;
073
074 public PaymentFileServiceImpl() {
075 super();
076 }
077
078 /**
079 * @see org.kuali.kfs.pdp.service.PaymentFileService#processPaymentFiles(org.kuali.kfs.sys.batch.BatchInputFileType)
080 */
081 public void processPaymentFiles(BatchInputFileType paymentInputFileType) {
082 List<String> fileNamesToLoad = batchInputFileService.listInputFileNamesWithDoneFile(paymentInputFileType);
083
084 for (String incomingFileName : fileNamesToLoad) {
085 try {
086 LOG.debug("processPaymentFiles() Processing " + incomingFileName);
087
088 // collect various information for status of load
089 LoadPaymentStatus status = new LoadPaymentStatus();
090 status.setErrorMap(new ErrorMap());
091
092 // process payment file
093 PaymentFileLoad paymentFile = processPaymentFile(paymentInputFileType, incomingFileName, status.getErrorMap());
094 if (paymentFile != null || paymentFile.isPassedValidation()) {
095 // load payment data
096 loadPayments(paymentFile, status, incomingFileName);
097 }
098
099 createOutputFile(status, incomingFileName);
100 }
101 catch (RuntimeException e) {
102 LOG.error("Caught exception trying to load payment file: " + incomingFileName, e);
103 // swallow exception so we can continue processing files, the errors have been reported by email
104 }
105 }
106 }
107
108 /**
109 * Attempt to parse the file, run validations, and store batch data
110 *
111 * @param paymentInputFileType <code>BatchInputFileType</code> for payment files
112 * @param incomingFileName name of payment file
113 * @param errorMap <code>Map</code> of errors
114 * @return <code>LoadPaymentStatus</code> containing status data for load
115 */
116 protected PaymentFileLoad processPaymentFile(BatchInputFileType paymentInputFileType, String incomingFileName, MessageMap errorMap) {
117 // parse xml, if errors found return with failure
118 PaymentFileLoad paymentFile = parsePaymentFile(paymentInputFileType, incomingFileName, errorMap);
119
120 if (errorMap.isEmpty()) {
121 // do validation
122 doPaymentFileValidation(paymentFile, errorMap);
123 }
124
125 return paymentFile;
126 }
127
128 /**
129 * @see org.kuali.kfs.pdp.service.PaymentFileService#doPaymentFileValidation(org.kuali.kfs.pdp.businessobject.PaymentFileLoad,
130 * org.kuali.rice.kns.util.ErrorMap)
131 */
132 public void doPaymentFileValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
133 paymentFileValidationService.doHardEdits(paymentFile, errorMap);
134
135 if (!errorMap.isEmpty()) {
136 paymentFileEmailService.sendErrorEmail(paymentFile, errorMap);
137 }
138
139 paymentFile.setPassedValidation(true);
140 }
141
142 /**
143 * @see org.kuali.kfs.pdp.service.PaymentFileService#loadPayments(java.lang.String)
144 */
145 public void loadPayments(PaymentFileLoad paymentFile, LoadPaymentStatus status, String incomingFileName) {
146 status.setChart(paymentFile.getChart());
147 status.setUnit(paymentFile.getUnit());
148 status.setSubUnit(paymentFile.getSubUnit());
149 status.setCreationDate(paymentFile.getCreationDate());
150 status.setDetailCount(paymentFile.getActualPaymentCount());
151 status.setDetailTotal(paymentFile.getCalculatedPaymentTotalAmount());
152
153 // create batch record for payment load
154 Batch batch = createNewBatch(paymentFile, getBaseFileName(incomingFileName));
155 businessObjectService.save(batch);
156
157 paymentFile.setBatchId(batch.getId());
158 status.setBatchId(batch.getId());
159
160 // do warnings and set defaults
161 List<String> warnings = paymentFileValidationService.doSoftEdits(paymentFile);
162 status.setWarnings(warnings);
163
164 // store groups
165 for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
166 businessObjectService.save(paymentGroup);
167 }
168
169 // send list of warnings
170 paymentFileEmailService.sendLoadEmail(paymentFile, warnings);
171 if (paymentFile.isTaxEmailRequired()) {
172 paymentFileEmailService.sendTaxEmail(paymentFile);
173 }
174
175 removeDoneFile(incomingFileName);
176
177 LOG.debug("loadPayments() was successful");
178 status.setLoadStatus(LoadPaymentStatus.LoadStatus.SUCCESS);
179 }
180
181 /**
182 * Calls <code>BatchInputFileService</code> to validate XML against schema and parse.
183 *
184 * @param paymentInputFileType <code>BatchInputFileType</code> for payment files
185 * @param incomingFileName name of the payment file to parse
186 * @param errorMap any errors encountered while parsing are adding to
187 * @return <code>PaymentFile</code> containing the parsed values
188 */
189 protected PaymentFileLoad parsePaymentFile(BatchInputFileType paymentInputFileType, String incomingFileName, MessageMap errorMap) {
190 FileInputStream fileContents;
191 try {
192 fileContents = new FileInputStream(incomingFileName);
193 }
194 catch (FileNotFoundException e1) {
195 LOG.error("file to load not found " + incomingFileName, e1);
196 throw new RuntimeException("Cannot find the file requested to be loaded " + incomingFileName, e1);
197 }
198
199 // do the parse
200 PaymentFileLoad paymentFile = null;
201 try {
202 byte[] fileByteContent = IOUtils.toByteArray(fileContents);
203 paymentFile = (PaymentFileLoad) batchInputFileService.parse(paymentInputFileType, fileByteContent);
204 }
205 catch (IOException e) {
206 LOG.error("error while getting file bytes: " + e.getMessage(), e);
207 throw new RuntimeException("Error encountered while attempting to get file bytes: " + e.getMessage(), e);
208 }
209 catch (ParseException e1) {
210 LOG.error("Error parsing xml " + e1.getMessage());
211
212 errorMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML, new String[] { e1.getMessage() });
213
214 // Send error email
215 paymentFileEmailService.sendErrorEmail(paymentFile, errorMap);
216 }
217
218 return paymentFile;
219 }
220
221 /**
222 * @see org.kuali.kfs.pdp.service.PaymentFileService#createOutputFile(org.kuali.kfs.pdp.businessobject.LoadPaymentStatus,
223 * java.lang.String)
224 */
225 public boolean createOutputFile(LoadPaymentStatus status, String inputFileName) {
226 // construct the outgoing file name
227 String filename = outgoingDirectoryName + "/" + getBaseFileName(inputFileName);
228
229 // set code-message indicating overall load status
230 String code;
231 String message;
232 if (LoadPaymentStatus.LoadStatus.SUCCESS.equals(status.getLoadStatus())) {
233 code = "SUCCESS";
234 message = "Successful Load";
235 }
236 else {
237 code = "FAIL";
238 message = "Load Failed: ";
239 List<ErrorMessage> errorMessages = status.getErrorMap().getMessages(KFSConstants.GLOBAL_ERRORS);
240 for (ErrorMessage errorMessage : errorMessages) {
241 String resourceMessage = kualiConfigurationService.getPropertyString(errorMessage.getErrorKey());
242 resourceMessage = MessageFormat.format(resourceMessage, (Object[]) errorMessage.getMessageParameters());
243 message += resourceMessage + ", ";
244 }
245 }
246
247 try {
248 FileOutputStream out = new FileOutputStream(filename);
249 PrintStream p = new PrintStream(out);
250
251 p.println("<pdp_load_status>");
252 p.println(" <input_file_name>" + inputFileName + "</input_file_name>");
253 p.println(" <code>" + code + "</code>");
254 p.println(" <count>" + status.getDetailCount() + "</count>");
255 if (status.getDetailTotal() != null) {
256 p.println(" <total>" + status.getDetailTotal() + "</total>");
257 }
258 else {
259 p.println(" <total>0</total>");
260 }
261
262 p.println(" <description>" + message + "</description>");
263 p.println(" <messages>");
264 for (String warning : status.getWarnings()) {
265 p.println(" <message>" + warning + "</message>");
266 }
267 p.println(" </messages>");
268 p.println("</pdp_load_status>");
269
270 p.close();
271 out.close();
272 }
273 catch (FileNotFoundException e) {
274 LOG.error("createOutputFile() Cannot create output file", e);
275 return false;
276 }
277 catch (IOException e) {
278 LOG.error("createOutputFile() Cannot write to output file", e);
279 return false;
280 }
281
282 return true;
283 }
284
285 /**
286 * Create a new <code>Batch</code> record for the payment file.
287 *
288 * @param paymentFile parsed payment file object
289 * @param fileName payment file name (without path)
290 * @return <code>Batch<code> object
291 */
292 protected Batch createNewBatch(PaymentFileLoad paymentFile, String fileName) {
293 Timestamp now = dateTimeService.getCurrentTimestamp();
294
295 Calendar nowPlus30 = Calendar.getInstance();
296 nowPlus30.setTime(now);
297 nowPlus30.add(Calendar.DATE, 30);
298
299 Calendar nowMinus30 = Calendar.getInstance();
300 nowMinus30.setTime(now);
301 nowMinus30.add(Calendar.DATE, -30);
302
303 Batch batch = new Batch();
304
305 CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
306 batch.setCustomerProfile(customer);
307 batch.setCustomerFileCreateTimestamp(new Timestamp(paymentFile.getCreationDate().getTime()));
308 batch.setFileProcessTimestamp(now);
309 batch.setPaymentCount(new KualiInteger(paymentFile.getPaymentCount()));
310
311 if (fileName.length() > 30) {
312 batch.setPaymentFileName(fileName.substring(0, 30));
313 }
314 else {
315 batch.setPaymentFileName(fileName);
316 }
317
318 batch.setPaymentTotalAmount(paymentFile.getPaymentTotalAmount());
319 batch.setSubmiterUserId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
320
321 return batch;
322 }
323
324
325 /**
326 * @returns the file name from the file full path.
327 */
328 protected String getBaseFileName(String filename) {
329 // Replace any backslashes with forward slashes. Works on Windows or Unix
330 filename = filename.replaceAll("\\\\", "/");
331
332 int startingPointer = filename.length() - 1;
333 while ((startingPointer > 0) && (filename.charAt(startingPointer) != '/')) {
334 startingPointer--;
335 }
336
337 return filename.substring(startingPointer + 1);
338 }
339
340 /**
341 * Clears out the associated .done file for the processed data file
342 *
343 * @param dataFileName the name of date file with done file to remove
344 */
345 protected void removeDoneFile(String dataFileName) {
346 File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + ".done");
347 if (doneFile.exists()) {
348 doneFile.delete();
349 }
350 }
351
352 /**
353 * Sets the outgoingDirectoryName attribute value.
354 *
355 * @param outgoingDirectoryName The outgoingDirectoryName to set.
356 */
357 public void setOutgoingDirectoryName(String outgoingDirectoryName) {
358 this.outgoingDirectoryName = outgoingDirectoryName;
359 }
360
361 /**
362 * Sets the parameterService attribute value.
363 *
364 * @param parameterService The parameterService to set.
365 */
366 public void setParameterService(ParameterService parameterService) {
367 this.parameterService = parameterService;
368 }
369
370 /**
371 * Sets the customerProfileService attribute value.
372 *
373 * @param customerProfileService The customerProfileService to set.
374 */
375 public void setCustomerProfileService(CustomerProfileService customerProfileService) {
376 this.customerProfileService = customerProfileService;
377 }
378
379 /**
380 * Sets the batchInputFileService attribute value.
381 *
382 * @param batchInputFileService The batchInputFileService to set.
383 */
384 public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
385 this.batchInputFileService = batchInputFileService;
386 }
387
388 /**
389 * Sets the paymentFileValidationService attribute value.
390 *
391 * @param paymentFileValidationService The paymentFileValidationService to set.
392 */
393 public void setPaymentFileValidationService(PaymentFileValidationService paymentFileValidationService) {
394 this.paymentFileValidationService = paymentFileValidationService;
395 }
396
397 /**
398 * Sets the businessObjectService attribute value.
399 *
400 * @param businessObjectService The businessObjectService to set.
401 */
402 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
403 this.businessObjectService = businessObjectService;
404 }
405
406 /**
407 * Sets the dateTimeService attribute value.
408 *
409 * @param dateTimeService The dateTimeService to set.
410 */
411 public void setDateTimeService(DateTimeService dateTimeService) {
412 this.dateTimeService = dateTimeService;
413 }
414
415 /**
416 * Sets the paymentFileEmailService attribute value.
417 *
418 * @param paymentFileEmailService The paymentFileEmailService to set.
419 */
420 public void setPaymentFileEmailService(PdpEmailService paymentFileEmailService) {
421 this.paymentFileEmailService = paymentFileEmailService;
422 }
423
424 /**
425 * Sets the kualiConfigurationService attribute value.
426 *
427 * @param kualiConfigurationService The kualiConfigurationService to set.
428 */
429 public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
430 this.kualiConfigurationService = kualiConfigurationService;
431 }
432
433 }
434