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