/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.pdp.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.pdp.businessobject.Batch;
import org.kuali.kfs.pdp.businessobject.CustomerProfile;
import org.kuali.kfs.pdp.businessobject.LoadPaymentStatus;
import org.kuali.kfs.pdp.businessobject.PaymentFileLoad;
import org.kuali.kfs.pdp.businessobject.PaymentGroup;
import org.kuali.kfs.pdp.service.CustomerProfileService;
import org.kuali.kfs.pdp.service.PaymentFileService;
import org.kuali.kfs.pdp.service.PaymentFileValidationService;
import org.kuali.kfs.pdp.service.PdpEmailService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.batch.BatchInputFileType;
import org.kuali.kfs.sys.batch.InitiateDirectoryBase;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiInteger;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.ErrorMessage;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.springframework.transaction.annotation.Transactional;
/**
* @see org.kuali.kfs.pdp.service.PaymentFileService
*/
@Transactional
public class PaymentFileServiceImpl extends InitiateDirectoryBase implements PaymentFileService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentFileServiceImpl.class);
private String outgoingDirectoryName;
private ParameterService parameterService;
private CustomerProfileService customerProfileService;
private BatchInputFileService batchInputFileService;
private PaymentFileValidationService paymentFileValidationService;
private BusinessObjectService businessObjectService;
private DateTimeService dateTimeService;
private PdpEmailService paymentFileEmailService;
private ConfigurationService kualiConfigurationService;
public PaymentFileServiceImpl() {
super();
}
/**
* @see org.kuali.kfs.pdp.service.PaymentFileService#processPaymentFiles(org.kuali.kfs.sys.batch.BatchInputFileType)
*/
@Override
public void processPaymentFiles(BatchInputFileType paymentInputFileType) {
List<String> fileNamesToLoad = batchInputFileService.listInputFileNamesWithDoneFile(paymentInputFileType);
for (String incomingFileName : fileNamesToLoad) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("processPaymentFiles() Processing " + incomingFileName);
}
// collect various information for status of load
LoadPaymentStatus status = new LoadPaymentStatus();
status.setMessageMap(new MessageMap());
// process payment file
PaymentFileLoad paymentFile = processPaymentFile(paymentInputFileType, incomingFileName, status.getMessageMap());
if (paymentFile != null && paymentFile.isPassedValidation()) {
// load payment data
loadPayments(paymentFile, status, incomingFileName);
createOutputFile(status, incomingFileName);
}else{
//if we encounter an error for the payment file, we will remove the .done file so it will not be parse again
LOG.error("Encounter a problem while processing payment file: " + incomingFileName + " . Removing the done file to stop re-process.");
removeDoneFile(incomingFileName);
}
}
catch (RuntimeException e) {
LOG.error("Caught exception trying to load payment file: " + incomingFileName, e);
// swallow exception so we can continue processing files, the errors have been reported by email
}
}
}
/**
* Attempt to parse the file, run validations, and store batch data
*
* @param paymentInputFileType <code>BatchInputFileType</code> for payment files
* @param incomingFileName name of payment file
* @param errorMap <code>Map</code> of errors
* @return <code>LoadPaymentStatus</code> containing status data for load
*/
protected PaymentFileLoad processPaymentFile(BatchInputFileType paymentInputFileType, String incomingFileName, MessageMap errorMap) {
// parse xml, if errors found return with failure
PaymentFileLoad paymentFile = parsePaymentFile(paymentInputFileType, incomingFileName, errorMap);
// if no parsing error, do further validation
if (errorMap.hasNoErrors()) {
doPaymentFileValidation(paymentFile, errorMap);
}
//TODO FSKD-5416 KFSCNTRB ???
// if any error from parsing or post-parsing validation, send error email notice
if (errorMap.hasErrors()) {
paymentFileEmailService.sendErrorEmail(paymentFile, errorMap);
}
return paymentFile;
}
/**
* @see org.kuali.kfs.pdp.service.PaymentFileService#doPaymentFileValidation(org.kuali.kfs.pdp.businessobject.PaymentFileLoad,
* org.kuali.rice.krad.util.MessageMap)
*/
@Override
public void doPaymentFileValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
paymentFileValidationService.doHardEdits(paymentFile, errorMap);
//TODO FSKD-5416 KFSCNTRB ???
if (errorMap.hasErrors()) {
// move the sending error email notice logic into the caller processPaymentFile
// since we need to send such notice on and both parsing error and post-parsing validation error
//paymentFileEmailService.sendErrorEmail(paymentFile, errorMap);
// set validation failed
paymentFile.setPassedValidation(false);
}
else {
// set validation succeeded
paymentFile.setPassedValidation(true);
}
}
/**
* @see org.kuali.kfs.pdp.service.PaymentFileService#loadPayments(java.lang.String)
*/
@Override
public void loadPayments(PaymentFileLoad paymentFile, LoadPaymentStatus status, String incomingFileName) {
status.setChart(paymentFile.getChart());
status.setUnit(paymentFile.getUnit());
status.setSubUnit(paymentFile.getSubUnit());
status.setCreationDate(paymentFile.getCreationDate());
status.setDetailCount(paymentFile.getActualPaymentCount());
status.setDetailTotal(paymentFile.getCalculatedPaymentTotalAmount());
// create batch record for payment load
Batch batch = createNewBatch(paymentFile, getBaseFileName(incomingFileName));
businessObjectService.save(batch);
paymentFile.setBatchId(batch.getId());
status.setBatchId(batch.getId());
// do warnings and set defaults
List<String> warnings = paymentFileValidationService.doSoftEdits(paymentFile);
status.setWarnings(warnings);
// store groups
for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
businessObjectService.save(paymentGroup);
}
// send list of warnings
paymentFileEmailService.sendLoadEmail(paymentFile, warnings);
if (paymentFile.isTaxEmailRequired()) {
paymentFileEmailService.sendTaxEmail(paymentFile);
}
removeDoneFile(incomingFileName);
LOG.debug("loadPayments() was successful");
status.setLoadStatus(LoadPaymentStatus.LoadStatus.SUCCESS);
}
/**
* Calls <code>BatchInputFileService</code> to validate XML against schema and parse.
*
* @param paymentInputFileType <code>BatchInputFileType</code> for payment files
* @param incomingFileName name of the payment file to parse
* @param errorMap any errors encountered while parsing are adding to
* @return <code>PaymentFile</code> containing the parsed values
*/
protected PaymentFileLoad parsePaymentFile(BatchInputFileType paymentInputFileType, String incomingFileName, MessageMap errorMap) {
FileInputStream fileContents;
try {
fileContents = new FileInputStream(incomingFileName);
}
catch (FileNotFoundException e1) {
LOG.error("file to load not found " + incomingFileName, e1);
throw new RuntimeException("Cannot find the file requested to be loaded " + incomingFileName, e1);
}
// do the parse
PaymentFileLoad paymentFile = null;
try {
byte[] fileByteContent = IOUtils.toByteArray(fileContents);
paymentFile = (PaymentFileLoad) batchInputFileService.parse(paymentInputFileType, fileByteContent);
}
catch (IOException e) {
LOG.error("error while getting file bytes: " + e.getMessage(), e);
throw new RuntimeException("Error encountered while attempting to get file bytes: " + e.getMessage(), e);
}
catch (ParseException e1) {
LOG.error("Error parsing xml " + e1.getMessage());
errorMap.putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML, new String[] { e1.getMessage() });
}
return paymentFile;
}
/**
* @see org.kuali.kfs.pdp.service.PaymentFileService#createOutputFile(org.kuali.kfs.pdp.businessobject.LoadPaymentStatus,
* java.lang.String)
*/
@Override
public boolean createOutputFile(LoadPaymentStatus status, String inputFileName) {
//add a step to check for directory paths
prepareDirectories(getRequiredDirectoryNames());
// construct the outgoing file name
String filename = outgoingDirectoryName + "/" + getBaseFileName(inputFileName);
// set code-message indicating overall load status
String code;
String message;
if (LoadPaymentStatus.LoadStatus.SUCCESS.equals(status.getLoadStatus())) {
code = "SUCCESS";
message = "Successful Load";
}
else {
code = "FAIL";
message = "Load Failed: ";
List<ErrorMessage> errorMessages = status.getMessageMap().getMessages(KFSConstants.GLOBAL_ERRORS);
for (ErrorMessage errorMessage : errorMessages) {
String resourceMessage = kualiConfigurationService.getPropertyValueAsString(errorMessage.getErrorKey());
resourceMessage = MessageFormat.format(resourceMessage, (Object[]) errorMessage.getMessageParameters());
message += resourceMessage + ", ";
}
}
try {
FileOutputStream out = new FileOutputStream(filename);
PrintStream p = new PrintStream(out);
p.println("<pdp_load_status>");
p.println(" <input_file_name>" + inputFileName + "</input_file_name>");
p.println(" <code>" + code + "</code>");
p.println(" <count>" + status.getDetailCount() + "</count>");
if (status.getDetailTotal() != null) {
p.println(" <total>" + status.getDetailTotal() + "</total>");
}
else {
p.println(" <total>0</total>");
}
p.println(" <description>" + message + "</description>");
p.println(" <messages>");
for (String warning : status.getWarnings()) {
p.println(" <message>" + warning + "</message>");
}
p.println(" </messages>");
p.println("</pdp_load_status>");
p.close();
out.close();
// creating .done file
File doneFile = new File(filename.substring(0, filename.lastIndexOf(".")) + ".done");
doneFile.createNewFile();
}
catch (FileNotFoundException e) {
LOG.error("createOutputFile() Cannot create output file", e);
return false;
}
catch (IOException e) {
LOG.error("createOutputFile() Cannot write to output file", e);
return false;
}
return true;
}
/**
* Create a new <code>Batch</code> record for the payment file.
*
* @param paymentFile parsed payment file object
* @param fileName payment file name (without path)
* @return <code>Batch<code> object
*/
protected Batch createNewBatch(PaymentFileLoad paymentFile, String fileName) {
Timestamp now = dateTimeService.getCurrentTimestamp();
Calendar nowPlus30 = Calendar.getInstance();
nowPlus30.setTime(now);
nowPlus30.add(Calendar.DATE, 30);
Calendar nowMinus30 = Calendar.getInstance();
nowMinus30.setTime(now);
nowMinus30.add(Calendar.DATE, -30);
Batch batch = new Batch();
CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
batch.setCustomerProfile(customer);
batch.setCustomerFileCreateTimestamp(new Timestamp(paymentFile.getCreationDate().getTime()));
batch.setFileProcessTimestamp(now);
batch.setPaymentCount(new KualiInteger(paymentFile.getPaymentCount()));
if (fileName.length() > 30) {
batch.setPaymentFileName(fileName.substring(0, 30));
}
else {
batch.setPaymentFileName(fileName);
}
batch.setPaymentTotalAmount(paymentFile.getPaymentTotalAmount());
batch.setSubmiterUserId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
return batch;
}
/**
* @returns the file name from the file full path.
*/
protected String getBaseFileName(String filename) {
// Replace any backslashes with forward slashes. Works on Windows or Unix
filename = filename.replaceAll("\\\\", "/");
int startingPointer = filename.length() - 1;
while ((startingPointer > 0) && (filename.charAt(startingPointer) != '/')) {
startingPointer--;
}
return filename.substring(startingPointer + 1);
}
/**
* Clears out the associated .done file for the processed data file
*
* @param dataFileName the name of date file with done file to remove
*/
protected void removeDoneFile(String dataFileName) {
File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + ".done");
if (doneFile.exists()) {
doneFile.delete();
}
}
/**
* Sets the outgoingDirectoryName attribute value.
*
* @param outgoingDirectoryName The outgoingDirectoryName to set.
*/
public void setOutgoingDirectoryName(String outgoingDirectoryName) {
this.outgoingDirectoryName = outgoingDirectoryName;
}
/**
* Sets the parameterService attribute value.
*
* @param parameterService The parameterService to set.
*/
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
/**
* Sets the customerProfileService attribute value.
*
* @param customerProfileService The customerProfileService to set.
*/
public void setCustomerProfileService(CustomerProfileService customerProfileService) {
this.customerProfileService = customerProfileService;
}
/**
* Sets the batchInputFileService attribute value.
*
* @param batchInputFileService The batchInputFileService to set.
*/
public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
this.batchInputFileService = batchInputFileService;
}
/**
* Sets the paymentFileValidationService attribute value.
*
* @param paymentFileValidationService The paymentFileValidationService to set.
*/
public void setPaymentFileValidationService(PaymentFileValidationService paymentFileValidationService) {
this.paymentFileValidationService = paymentFileValidationService;
}
/**
* Sets the businessObjectService attribute value.
*
* @param businessObjectService The businessObjectService to set.
*/
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
/**
* Sets the dateTimeService attribute value.
*
* @param dateTimeService The dateTimeService to set.
*/
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
/**
* Sets the paymentFileEmailService attribute value.
*
* @param paymentFileEmailService The paymentFileEmailService to set.
*/
public void setPaymentFileEmailService(PdpEmailService paymentFileEmailService) {
this.paymentFileEmailService = paymentFileEmailService;
}
/**
* Sets the kualiConfigurationService attribute value.
*
* @param kualiConfigurationService The kualiConfigurationService to set.
*/
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
/**
* @see org.kuali.kfs.sys.batch.service.impl.InitiateDirectoryImpl#getRequiredDirectoryNames()
*/
@Override
public List<String> getRequiredDirectoryNames() {
return new ArrayList<String>() {{add(outgoingDirectoryName); }};
}
}