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