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.module.ar.batch.service.impl; 017 018 import java.awt.Color; 019 import java.io.BufferedOutputStream; 020 import java.io.File; 021 import java.io.FileInputStream; 022 import java.io.FileNotFoundException; 023 import java.io.FileOutputStream; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.text.MessageFormat; 027 import java.text.SimpleDateFormat; 028 import java.util.ArrayList; 029 import java.util.List; 030 import java.util.Set; 031 032 import org.apache.commons.beanutils.BeanUtils; 033 import org.apache.commons.beanutils.PropertyUtils; 034 import org.apache.commons.io.IOUtils; 035 import org.apache.commons.lang.StringUtils; 036 import org.kuali.kfs.coa.service.OrganizationService; 037 import org.kuali.kfs.module.ar.ArConstants; 038 import org.kuali.kfs.module.ar.ArKeyConstants; 039 import org.kuali.kfs.module.ar.batch.CustomerLoadStep; 040 import org.kuali.kfs.module.ar.batch.report.CustomerLoadBatchErrors; 041 import org.kuali.kfs.module.ar.batch.report.CustomerLoadFileResult; 042 import org.kuali.kfs.module.ar.batch.report.CustomerLoadResult; 043 import org.kuali.kfs.module.ar.batch.report.CustomerLoadResult.ResultCode; 044 import org.kuali.kfs.module.ar.batch.service.CustomerLoadService; 045 import org.kuali.kfs.module.ar.batch.vo.CustomerDigesterAdapter; 046 import org.kuali.kfs.module.ar.batch.vo.CustomerDigesterVO; 047 import org.kuali.kfs.module.ar.businessobject.Customer; 048 import org.kuali.kfs.module.ar.businessobject.CustomerAddress; 049 import org.kuali.kfs.module.ar.document.service.CustomerService; 050 import org.kuali.kfs.module.ar.document.service.SystemInformationService; 051 import org.kuali.kfs.module.ar.document.validation.impl.CustomerRule; 052 import org.kuali.kfs.sys.KFSConstants; 053 import org.kuali.kfs.sys.KFSKeyConstants; 054 import org.kuali.kfs.sys.batch.BatchInputFileType; 055 import org.kuali.kfs.sys.batch.service.BatchInputFileService; 056 import org.kuali.kfs.sys.exception.ParseException; 057 import org.kuali.rice.kew.exception.WorkflowException; 058 import org.kuali.rice.kns.document.MaintenanceDocument; 059 import org.kuali.rice.kns.document.MaintenanceDocumentBase; 060 import org.kuali.rice.kns.service.BusinessObjectService; 061 import org.kuali.rice.kns.service.DateTimeService; 062 import org.kuali.rice.kns.service.DocumentService; 063 import org.kuali.rice.kns.service.KualiConfigurationService; 064 import org.kuali.rice.kns.service.ParameterService; 065 import org.kuali.rice.kns.util.ErrorMessage; 066 import org.kuali.rice.kns.util.GlobalVariables; 067 import org.kuali.rice.kns.util.KNSConstants; 068 import org.kuali.rice.kns.util.MessageMap; 069 070 import com.lowagie.text.Chunk; 071 import com.lowagie.text.Document; 072 import com.lowagie.text.DocumentException; 073 import com.lowagie.text.Element; 074 import com.lowagie.text.Font; 075 import com.lowagie.text.FontFactory; 076 import com.lowagie.text.PageSize; 077 import com.lowagie.text.Paragraph; 078 import com.lowagie.text.pdf.PdfWriter; 079 080 public class CustomerLoadServiceImpl implements CustomerLoadService { 081 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CustomerLoadServiceImpl.class); 082 083 private static final String MAX_RECORDS_PARM_NAME = "MAX_NUMBER_OF_RECORDS_PER_DOCUMENT"; 084 private static final String NA = "-- N/A --"; 085 private static final String WORKFLOW_DOC_ID_PREFIX = " - WITH WORKFLOW DOCID: "; 086 087 private BatchInputFileService batchInputFileService; 088 private CustomerService customerService; 089 private KualiConfigurationService configService; 090 private DocumentService docService; 091 private ParameterService parameterService; 092 private OrganizationService orgService; 093 private SystemInformationService sysInfoService; 094 private BusinessObjectService boService; 095 private DateTimeService dateTimeService; 096 097 private BatchInputFileType batchInputFileType; 098 private CustomerDigesterAdapter adapter; 099 private String reportsDirectory; 100 101 public CustomerLoadServiceImpl() { 102 } 103 104 public boolean loadFiles() { 105 106 LOG.info("Beginning processing of all available files for AR Customer Batch Upload."); 107 108 boolean result = true; 109 List<CustomerLoadFileResult> fileResults = new ArrayList<CustomerLoadFileResult>(); 110 CustomerLoadFileResult reporter = null; 111 112 // create a list of the files to process 113 List<String> fileNamesToLoad = getListOfFilesToProcess(); 114 LOG.info("Found " + fileNamesToLoad.size() + " file(s) to process."); 115 116 // process each file in turn 117 List<String> processedFiles = new ArrayList<String>(); 118 for (String inputFileName : fileNamesToLoad) { 119 120 LOG.info("Beginning processing of filename: " + inputFileName + "."); 121 122 // setup the results reporting 123 reporter = new CustomerLoadFileResult(inputFileName); 124 fileResults.add(reporter); 125 126 if (loadFile(inputFileName, reporter)) { 127 result &= true; 128 reporter.addFileInfoMessage("File successfully completed processing."); 129 processedFiles.add(inputFileName); 130 } 131 else { 132 reporter.addFileErrorMessage("File failed to process successfully."); 133 result &= false; 134 } 135 } 136 137 // remove done files 138 removeDoneFiles(processedFiles); 139 140 // write report PDF 141 writeReportPDF(fileResults); 142 143 return result; 144 } 145 146 protected List<String> getListOfFilesToProcess() { 147 148 // create a list of the files to process 149 List<String> fileNamesToLoad = batchInputFileService.listInputFileNamesWithDoneFile(batchInputFileType); 150 151 if (fileNamesToLoad == null) { 152 LOG.error("BatchInputFileService.listInputFileNamesWithDoneFile(" + 153 batchInputFileType.getFileTypeIdentifer() + ") returned NULL which should never happen."); 154 throw new RuntimeException("BatchInputFileService.listInputFileNamesWithDoneFile(" + 155 batchInputFileType.getFileTypeIdentifer() + ") returned NULL which should never happen."); 156 } 157 158 // filenames returned should never be blank/empty/null 159 for (String inputFileName : fileNamesToLoad) { 160 if (StringUtils.isBlank(inputFileName)) { 161 LOG.error("One of the file names returned as ready to process [" + inputFileName + 162 "] was blank. This should not happen, so throwing an error to investigate."); 163 throw new RuntimeException("One of the file names returned as ready to process [" + inputFileName + 164 "] was blank. This should not happen, so throwing an error to investigate."); 165 } 166 } 167 168 return fileNamesToLoad; 169 } 170 171 /** 172 * Clears out associated .done files for the processed data files. 173 * 174 * @param dataFileNames 175 */ 176 protected void removeDoneFiles(List<String> dataFileNames) { 177 for (String dataFileName : dataFileNames) { 178 File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + ".done"); 179 if (doneFile.exists()) { 180 doneFile.delete(); 181 } 182 } 183 } 184 185 /** 186 * 187 * @see org.kuali.kfs.module.ar.batch.service.CustomerLoadService#loadFile(java.lang.String) 188 */ 189 public boolean loadFile(String fileName) { 190 return loadFile(fileName, new CustomerLoadFileResult(fileName)); 191 } 192 193 public boolean loadFile(String fileName, CustomerLoadFileResult reporter) { 194 195 boolean result = true; 196 197 //TODO move up to the loadFiles() method 198 List<String> routedDocumentNumbers = new ArrayList<String>(); 199 List<String> failedDocumentNumbers = new ArrayList<String>(); 200 201 // load up the file into a byte array 202 byte[] fileByteContent = safelyLoadFileBytes(fileName); 203 204 // parse the file against the XSD schema and load it into an object 205 LOG.info("Attempting to parse the file using Apache Digester."); 206 Object parsedObject = null; 207 try { 208 parsedObject = batchInputFileService.parse(batchInputFileType, fileByteContent); 209 } 210 catch (ParseException e) { 211 LOG.error("Error parsing batch file: " + e.getMessage()); 212 reporter.addFileErrorMessage("Error parsing batch file: " + e.getMessage()); 213 throw new ParseException(e.getMessage()); 214 } 215 216 // make sure we got the type we expected, then cast it 217 if (!(parsedObject instanceof List)) { 218 LOG.error("Parsed file was not of the expected type. Expected [" + List.class + "] but got [" + parsedObject.getClass() + "]."); 219 reporter.addFileErrorMessage("Parsed file was not of the expected type. Expected [" + List.class + "] but got [" + parsedObject.getClass() + "]."); 220 throw new RuntimeException("Parsed file was not of the expected type. Expected [" + List.class + "] but got [" + parsedObject.getClass() + "]."); 221 } 222 223 // prepare a list for the regular validate() method 224 List<CustomerDigesterVO> customerVOs = (List<CustomerDigesterVO>) parsedObject; 225 226 List<MaintenanceDocument> readyTransientDocs = new ArrayList<MaintenanceDocument>(); 227 LOG.info("Beginning validation and preparation of batch file."); 228 result = validateCustomers(customerVOs, readyTransientDocs, reporter, false); 229 230 // send the readyDocs into workflow 231 result &= sendDocumentsIntoWorkflow(readyTransientDocs, routedDocumentNumbers, failedDocumentNumbers, reporter); 232 233 return result; 234 } 235 236 protected boolean sendDocumentsIntoWorkflow(List<MaintenanceDocument> readyTransientDocs, List<String> routedDocumentNumbers, 237 List<String> failedDocumentNumbers, CustomerLoadFileResult reporter) { 238 boolean result = true; 239 for (MaintenanceDocument readyTransientDoc : readyTransientDocs) { 240 result &= sendDocumentIntoWorkflow(readyTransientDoc, routedDocumentNumbers, failedDocumentNumbers, reporter); 241 } 242 return result; 243 } 244 245 protected boolean sendDocumentIntoWorkflow(MaintenanceDocument readyTransientDoc, List<String> routedDocumentNumbers, 246 List<String> failedDocumentNumbers, CustomerLoadFileResult reporter) { 247 boolean result = true; 248 249 String customerName = ((Customer) readyTransientDoc.getNewMaintainableObject().getBusinessObject()).getCustomerName(); 250 251 // create a real workflow document 252 MaintenanceDocument realMaintDoc; 253 try { 254 realMaintDoc = (MaintenanceDocument) docService.getNewDocument(getCustomerMaintenanceDocumentTypeName()); 255 } 256 catch (WorkflowException e) { 257 LOG.error("WorkflowException occurred while trying to create a new MaintenanceDocument.", e); 258 throw new RuntimeException("WorkflowException occurred while trying to create a new MaintenanceDocument.", e); 259 } 260 261 realMaintDoc.getNewMaintainableObject().setBusinessObject(readyTransientDoc.getNewMaintainableObject().getBusinessObject()); 262 realMaintDoc.getOldMaintainableObject().setBusinessObject(readyTransientDoc.getOldMaintainableObject().getBusinessObject()); 263 realMaintDoc.getNewMaintainableObject().setMaintenanceAction(readyTransientDoc.getNewMaintainableObject().getMaintenanceAction()); 264 realMaintDoc.getDocumentHeader().setDocumentDescription(readyTransientDoc.getDocumentHeader().getDocumentDescription()); 265 266 Customer customer = (Customer) realMaintDoc.getNewMaintainableObject().getBusinessObject(); 267 LOG.info("Routing Customer Maintenance document for [" + customer.getCustomerNumber() + "] " + customer.getCustomerName()); 268 269 try { 270 docService.routeDocument(realMaintDoc, "Routed Edit/Update Customer Maintenance from CustomerLoad Batch Process", null); 271 } 272 catch (WorkflowException e) { 273 LOG.error("WorkflowException occurred while trying to route a new MaintenanceDocument.", e); 274 reporter.addCustomerErrorMessage(customerName, "WorkflowException occurred while trying to route a new MaintenanceDocument: " + e.getMessage()); 275 result = false; 276 } 277 278 if (result == true) { 279 reporter.setCustomerSuccessResult(customerName); 280 reporter.setCustomerWorkflowDocId(customerName, realMaintDoc.getDocumentNumber()); 281 routedDocumentNumbers.add(realMaintDoc.getDocumentNumber()); 282 } 283 else { 284 reporter.setCustomerFailureResult(customerName); 285 failedDocumentNumbers.add(realMaintDoc.getDocumentNumber()); 286 } 287 return result; 288 } 289 290 protected String getCustomerMaintenanceDocumentTypeName() { 291 return "CUS"; 292 } 293 294 protected void addError(CustomerLoadBatchErrors batchErrors, String customerName, String propertyName, Class<?> propertyClass, String origValue, String description) { 295 batchErrors.addError(customerName, propertyName, propertyClass, origValue, description); 296 } 297 298 protected void addBatchErrorsToGlobalVariables(CustomerLoadBatchErrors batchErrors) { 299 Set<String> errorMessages = batchErrors.getErrorStrings(); 300 for (String errorMessage : errorMessages) { 301 GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, 302 KFSKeyConstants.ERROR_BATCH_UPLOAD_SAVE, errorMessage); 303 } 304 } 305 306 protected void addBatchErrorstoCustomerLoadResult(CustomerLoadBatchErrors batchErrors, CustomerLoadResult result) { 307 Set<String> errorMessages = batchErrors.getErrorStrings(); 308 for (String errorMessage : errorMessages) { 309 result.addErrorMessage(errorMessage); 310 } 311 } 312 313 /** 314 * 315 * Accepts a file name and returns a byte-array of the file name contents, if possible. 316 * 317 * Throws RuntimeExceptions if FileNotFound or IOExceptions occur. 318 * 319 * @param fileName String containing valid path & filename (relative or absolute) of file to load. 320 * @return A Byte Array of the contents of the file. 321 */ 322 protected byte[] safelyLoadFileBytes(String fileName) { 323 324 InputStream fileContents; 325 byte[] fileByteContent; 326 try { 327 fileContents = new FileInputStream(fileName); 328 } 329 catch (FileNotFoundException e1) { 330 LOG.error("Batch file not found [" + fileName + "]. " + e1.getMessage()); 331 throw new RuntimeException("Batch File not found [" + fileName + "]. " + e1.getMessage()); 332 } 333 try { 334 fileByteContent = IOUtils.toByteArray(fileContents); 335 } 336 catch (IOException e1) { 337 LOG.error("IO Exception loading: [" + fileName + "]. " + e1.getMessage()); 338 throw new RuntimeException("IO Exception loading: [" + fileName + "]. " + e1.getMessage()); 339 } 340 return fileByteContent; 341 } 342 343 /** 344 * The results of this method follow the same rules as the batch step result rules: 345 * 346 * The execution of this method may have 3 possible outcomes: 347 * 348 * 1. returns true, meaning that everything has succeeded, and dependent steps can continue running. No 349 * errors should be added to GlobalVariables.getMessageMap(). 350 * 351 * 2. returns false, meaning that some (but not necessarily all) steps have succeeded, and dependent 352 * steps can continue running. Details can be found in the GlobalVariables.getMessageMap(). 353 * 354 * 3. throws an exception, meaning that the step has failed, that the rest of the steps in a job should 355 * not be run, and that the job has failed. There may be errors in the GlobalVariables.getMessageMap(). 356 * 357 * @see org.kuali.kfs.module.ar.batch.service.CustomerLoadService#validate(java.util.List) 358 */ 359 public boolean validate(List<CustomerDigesterVO> customerUploads) { 360 return validateAndPrepare(customerUploads, new ArrayList<MaintenanceDocument>(), true); 361 } 362 363 /** 364 * @see org.kuali.kfs.module.ar.batch.service.CustomerLoadService#validateAndPrepare(java.util.List, java.util.List, boolean) 365 */ 366 public boolean validateAndPrepare(List<CustomerDigesterVO> customerUploads, List<MaintenanceDocument> customerMaintDocs, boolean useGlobalErrorMap) { 367 return validateCustomers(customerUploads, customerMaintDocs, new CustomerLoadFileResult(), useGlobalErrorMap); 368 } 369 370 /** 371 * 372 * Validate the customers lists 373 * 374 * @param customerUploads 375 * @param customerMaintDocs 376 * @param reporter 377 * @param useGlobalErrorMap 378 * @return 379 */ 380 protected boolean validateCustomers(List<CustomerDigesterVO> customerUploads, List<MaintenanceDocument> customerMaintDocs, CustomerLoadFileResult reporter, boolean useGlobalErrorMap) { 381 382 // fail if empty or null list 383 if (customerUploads == null) { 384 LOG.error("Null list of Customer upload objects. This should never happen."); 385 throw new IllegalArgumentException("Null list of Customer upload objects. This should never happen."); 386 } 387 if (customerUploads.isEmpty()) { 388 reporter.addFileErrorMessage("An empty list of Customer uploads was passed in for validation. As a result, no validation can be done."); 389 if (useGlobalErrorMap) { 390 GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_BATCH_UPLOAD_SAVE, new String[] { "An empty list of Customer uploads was passed in for validation. As a result, no validation was done." }); 391 } 392 return false; 393 } 394 395 boolean groupSucceeded = true; 396 boolean docSucceeded = true; 397 398 // check to make sure the input file doesnt have more docs than we allow in one batch file 399 String maxRecordsString = parameterService.getParameterValue(CustomerLoadStep.class, MAX_RECORDS_PARM_NAME); 400 if (StringUtils.isBlank(maxRecordsString) || !StringUtils.isNumeric(maxRecordsString)) { 401 LOG.error("Expected 'Max Records Per Document' System Parameter is not available."); 402 throw new RuntimeException("Expected 'Max Records Per Document' System Parameter is not available."); 403 } 404 Integer maxRecords = new Integer(maxRecordsString); 405 if (customerUploads.size() > maxRecords.intValue()) { 406 LOG.error("Too many records passed in for this file. " + customerUploads.size() + " were passed in, and the limit is " + maxRecords + ". As a result, no validation was done."); 407 reporter.addFileErrorMessage("Too many records passed in for this file. " + customerUploads.size() + " were passed in, and the limit is " + maxRecords + ". As a result, no validation was done."); 408 if (useGlobalErrorMap) { 409 GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_BATCH_UPLOAD_SAVE, new String[] { "Too many records passed in for this file. " + customerUploads.size() + " were passed in, and the limit is " + maxRecords + ". As a result, no validation was done." }); 410 } 411 return false; 412 } 413 414 // we have to create one real maint doc for the whole thing to pass the maintainable.checkAuthorizationRestrictions 415 MaintenanceDocument oneRealMaintDoc = null; 416 417 Customer customer = null; 418 CustomerLoadBatchErrors fileBatchErrors = new CustomerLoadBatchErrors(); 419 CustomerLoadBatchErrors customerBatchErrors; 420 String customerName; 421 if (adapter == null) adapter = new CustomerDigesterAdapter(); 422 for (CustomerDigesterVO customerDigesterVO : customerUploads) { 423 424 docSucceeded = true; 425 customerName = customerDigesterVO.getCustomerName(); 426 427 // setup logging and reporting 428 LOG.info("Beginning conversion and validation for [" + customerName + "]."); 429 reporter.addCustomerInfoMessage(customerName, "Beginning conversion and validation."); 430 CustomerLoadResult result = reporter.getCustomer(customerName); 431 customerBatchErrors = new CustomerLoadBatchErrors(); 432 433 // convert the VO to a BO 434 LOG.info("Beginning conversion from VO to BO."); 435 customer = adapter.convert(customerDigesterVO, customerBatchErrors); 436 437 // if any errors were generated, add them to the GlobalVariables, and return false 438 if (!customerBatchErrors.isEmpty()) { 439 LOG.info("The customer [" + customerName + "] was not processed due to errors in uploading and conversion."); 440 customerBatchErrors.addError(customerName, "Global", Object.class, "", "This document was not processed due to errors in uploading and conversion."); 441 addBatchErrorstoCustomerLoadResult(customerBatchErrors, result); 442 reporter.setCustomerFailureResult(customerName); 443 docSucceeded = false; 444 groupSucceeded &= false; 445 continue; 446 } 447 448 // determine whether this is an Update or a New 449 Customer existingCustomer = customerAlreadyExists(customer); 450 boolean isNew = (existingCustomer == null); 451 boolean isUpdate = !isNew; 452 453 // do some housekeeping 454 processBeforeValidating(customer, existingCustomer, isUpdate); 455 456 // create the transient maint doc 457 MaintenanceDocument transientMaintDoc = createTransientMaintDoc(); 458 459 // make sure we have the one real maint doc (to steal its document id) 460 oneRealMaintDoc = createRealMaintDoc(oneRealMaintDoc); 461 462 // steal the doc id from the real doc 463 transientMaintDoc.setDocumentNumber(oneRealMaintDoc.getDocumentNumber()); 464 transientMaintDoc.setDocumentHeader(oneRealMaintDoc.getDocumentHeader()); 465 transientMaintDoc.getDocumentHeader().setDocumentDescription("AR Customer Load Batch Transient"); 466 467 // set the old and new 468 transientMaintDoc.getNewMaintainableObject().setBusinessObject(customer); 469 transientMaintDoc.getOldMaintainableObject().setBusinessObject((existingCustomer == null ? new Customer() : existingCustomer )); 470 471 // set the maintainable actions, so isNew and isEdit on the maint doc return correct values 472 if (isNew) { 473 transientMaintDoc.getNewMaintainableObject().setMaintenanceAction(KNSConstants.MAINTENANCE_NEW_ACTION); 474 } 475 else { 476 transientMaintDoc.getNewMaintainableObject().setMaintenanceAction(KNSConstants.MAINTENANCE_EDIT_ACTION); 477 } 478 479 // report whether the customer is an Add or an Edit 480 if (isNew) { 481 reporter.addCustomerInfoMessage(customerName, "Customer record batched is a New Customer."); 482 } 483 else { 484 reporter.addCustomerInfoMessage(customerName, "Customer record batched is an Update to an existing Customer."); 485 } 486 487 // validate the batched customer 488 if (!validateSingle(transientMaintDoc, customerBatchErrors, customerName)) { 489 groupSucceeded &= false; 490 docSucceeded = false; 491 reporter.setCustomerFailureResult(customerName); 492 } 493 addBatchErrorstoCustomerLoadResult(customerBatchErrors, result); 494 495 // if the doc succeeded then add it to the list to be routed, and report it as successful 496 if (docSucceeded) { 497 customerMaintDocs.add(transientMaintDoc); 498 Customer customer2 = (Customer) transientMaintDoc.getNewMaintainableObject().getBusinessObject(); 499 reporter.addCustomerInfoMessage(customerName, "Customer Number is: " + customer2.getCustomerNumber()); 500 reporter.addCustomerInfoMessage(customerName, "Customer Name is: " + customer2.getCustomerName()); 501 reporter.setCustomerSuccessResult(customerName); 502 } 503 504 fileBatchErrors.addAll(customerBatchErrors); 505 } 506 507 // put any errors back in global vars 508 if (useGlobalErrorMap) { 509 addBatchErrorsToGlobalVariables(fileBatchErrors); 510 } 511 512 return groupSucceeded; 513 } 514 515 /** 516 * pre-processing for existing and new customer 517 * 518 * @param customer 519 * @param existingCustomer 520 * @param isUpdate 521 */ 522 protected void processBeforeValidating(Customer customer, Customer existingCustomer, boolean isUpdate) { 523 524 //update specifics processing 525 if (isUpdate) { 526 // if its has no customerNumber, then set it from existing record 527 if (StringUtils.isBlank(customer.getCustomerNumber())) { 528 customer.setCustomerNumber(existingCustomer.getCustomerNumber()); 529 } 530 531 // carry forward the version number 532 customer.setVersionNumber(existingCustomer.getVersionNumber()); 533 534 // don't let the batch zero out certain key fields on an update 535 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerTypeCode"); 536 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerTaxTypeCode"); 537 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerTaxNbr"); 538 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerCreditLimitAmount"); 539 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerCreditApprovedByName"); 540 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerParentCompanyNumber"); 541 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerPhoneNumber"); 542 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customer800PhoneNumber"); 543 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerContactName"); 544 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerContactPhoneNumber"); 545 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerFaxNumber"); 546 dontBlankOutFieldsOnUpdate(customer, existingCustomer, "customerBirthDate"); 547 } 548 549 // upper case important fields 550 upperCaseKeyFields(customer); 551 552 //NOTE: What's the reason for determining primary address?? address isn't used afterward 553 // determine whether the batch has a primary address, and which one it is 554 boolean batchHasPrimaryAddress = false; 555 CustomerAddress batchPrimaryAddress = null; 556 for (CustomerAddress address : customer.getCustomerAddresses()) { 557 if (ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY.equalsIgnoreCase(address.getCustomerAddressTypeCode())) { 558 batchHasPrimaryAddress = true; 559 batchPrimaryAddress = address; 560 } 561 } 562 563 // if its an update, merge the address records (ie, only add or update, dont remove all addresses not imported). 564 if (isUpdate) { 565 boolean addressInBatchCustomer = false; 566 List<CustomerAddress> newCusomterAddresses = customer.getCustomerAddresses(); 567 568 // populate a stub address list (with empty addresses) base on the new customer address list size 569 List<CustomerAddress> stubAddresses = new ArrayList<CustomerAddress>(); 570 for (CustomerAddress batchAddress : newCusomterAddresses) { 571 stubAddresses.add(new CustomerAddress()); 572 } 573 574 for (CustomerAddress existingAddress : existingCustomer.getCustomerAddresses()) { 575 addressInBatchCustomer = false; 576 for (CustomerAddress batchAddress : newCusomterAddresses) { 577 if (!addressInBatchCustomer && existingAddress.compareTo(batchAddress) == 0) { 578 addressInBatchCustomer = true; 579 } 580 } 581 582 if (!addressInBatchCustomer) { 583 584 //clone the address to avoid changing the existingAddress's type code 585 CustomerAddress clonedExistingAddress = cloneCustomerAddress(existingAddress); 586 // make sure we don't add a second Primary address, if the batch specifies a primary address, it wins 587 if (batchHasPrimaryAddress && ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY.equalsIgnoreCase(clonedExistingAddress.getCustomerAddressTypeCode())) { 588 clonedExistingAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); 589 } 590 customer.getCustomerAddresses().add(clonedExistingAddress); 591 }else{ 592 //found a address already in batch, remove one stub address from the list 593 stubAddresses.remove(0); 594 } 595 } 596 597 //append existing list to the stub list in order to have matching number of address for display, so the merged address from existing list is matched up 598 stubAddresses.addAll(existingCustomer.getCustomerAddresses()); 599 // reset existing customer's address to the stub address list 600 existingCustomer.setCustomerAddresses(stubAddresses); 601 } 602 603 // set parent customer number to null if blank (otherwise foreign key rule fails) 604 if (StringUtils.isBlank(customer.getCustomerParentCompanyNumber())) { 605 customer.setCustomerParentCompanyNumber(null); 606 } 607 608 } 609 610 /** 611 * Clone the address object 612 * 613 * @param address 614 * @return 615 */ 616 private CustomerAddress cloneCustomerAddress(CustomerAddress address) { 617 CustomerAddress clonedAddress = null; 618 try { 619 clonedAddress = (CustomerAddress) BeanUtils.cloneBean(address); 620 } 621 catch (Exception ex) { 622 LOG.error("Unable to clone address [" + address + "]", ex); 623 } 624 return clonedAddress; 625 } 626 627 protected void upperCaseKeyFields(Customer customer) { 628 629 // customer name 630 if (StringUtils.isNotBlank(customer.getCustomerName())) { 631 customer.setCustomerName(customer.getCustomerName().toUpperCase()); 632 } 633 634 // customer number 635 if (StringUtils.isNotBlank(customer.getCustomerNumber())) { 636 customer.setCustomerNumber(customer.getCustomerNumber().toUpperCase()); 637 } 638 639 // parent company number 640 if (StringUtils.isNotBlank(customer.getCustomerParentCompanyNumber())) { 641 customer.setCustomerParentCompanyNumber(customer.getCustomerParentCompanyNumber().toUpperCase()); 642 } 643 644 // customer tax type code 645 if (StringUtils.isNotBlank(customer.getCustomerTaxTypeCode())) { 646 customer.setCustomerTaxTypeCode(customer.getCustomerTaxTypeCode().toUpperCase()); 647 } 648 649 // customer tax number 650 if (StringUtils.isNotBlank(customer.getCustomerTaxNbr())) { 651 customer.setCustomerTaxNbr(customer.getCustomerTaxNbr().toUpperCase()); 652 } 653 654 // customer contact name 655 if (StringUtils.isNotBlank(customer.getCustomerContactName())) { 656 customer.setCustomerContactName(customer.getCustomerContactName().toUpperCase()); 657 } 658 659 // customer credit approved by name 660 if (StringUtils.isNotBlank(customer.getCustomerCreditApprovedByName())) { 661 customer.setCustomerCreditApprovedByName(customer.getCustomerCreditApprovedByName().toUpperCase()); 662 } 663 664 // customer email address 665 if (StringUtils.isNotBlank(customer.getCustomerEmailAddress())) { 666 customer.setCustomerEmailAddress(customer.getCustomerEmailAddress().toUpperCase()); 667 } 668 669 for (CustomerAddress address : customer.getCustomerAddresses()) { 670 671 if (address == null) continue; 672 673 // customer number 674 if (StringUtils.isNotBlank(address.getCustomerNumber())) { 675 address.setCustomerNumber(address.getCustomerNumber().toUpperCase()); 676 } 677 678 // customer address name 679 if (StringUtils.isNotBlank(address.getCustomerAddressName())) { 680 address.setCustomerAddressName(address.getCustomerAddressName().toUpperCase()); 681 } 682 683 // customerLine1StreetAddress 684 if (StringUtils.isNotBlank(address.getCustomerLine1StreetAddress())) { 685 address.setCustomerLine1StreetAddress(address.getCustomerLine1StreetAddress().toUpperCase()); 686 } 687 688 // customerLine2StreetAddress 689 if (StringUtils.isNotBlank(address.getCustomerLine2StreetAddress())) { 690 address.setCustomerLine2StreetAddress(address.getCustomerLine2StreetAddress().toUpperCase()); 691 } 692 693 // customerCityName 694 if (StringUtils.isNotBlank(address.getCustomerCityName())) { 695 address.setCustomerCityName(address.getCustomerCityName().toUpperCase()); 696 } 697 698 // customerStateCode 699 if (StringUtils.isNotBlank(address.getCustomerStateCode())) { 700 address.setCustomerStateCode(address.getCustomerStateCode().toUpperCase()); 701 } 702 703 // customerZipCode 704 if (StringUtils.isNotBlank(address.getCustomerZipCode())) { 705 address.setCustomerZipCode(address.getCustomerZipCode().toUpperCase()); 706 } 707 708 // customerCountryCode 709 if (StringUtils.isNotBlank(address.getCustomerNumber())) { 710 address.setCustomerNumber(address.getCustomerNumber().toUpperCase()); 711 } 712 713 // customerAddressInternationalProvinceName 714 if (StringUtils.isNotBlank(address.getCustomerAddressInternationalProvinceName())) { 715 address.setCustomerAddressInternationalProvinceName(address.getCustomerAddressInternationalProvinceName().toUpperCase()); 716 } 717 718 // customerInternationalMailCode 719 if (StringUtils.isNotBlank(address.getCustomerInternationalMailCode())) { 720 address.setCustomerInternationalMailCode(address.getCustomerInternationalMailCode().toUpperCase()); 721 } 722 723 // customerEmailAddress 724 if (StringUtils.isNotBlank(address.getCustomerEmailAddress())) { 725 address.setCustomerEmailAddress(address.getCustomerEmailAddress().toUpperCase()); 726 } 727 728 // customerAddressTypeCode 729 if (StringUtils.isNotBlank(address.getCustomerAddressTypeCode())) { 730 address.setCustomerAddressTypeCode(address.getCustomerAddressTypeCode().toUpperCase()); 731 } 732 733 } 734 } 735 736 /** 737 * 738 * This messy thing attempts to compare a property on the batch customer (new) and existing customer, and if 739 * the new is blank, but the old is there, to overwrite the new-value with the old-value, thus preventing 740 * batch uploads from blanking out certain fields. 741 * 742 * @param batchCustomer 743 * @param existingCustomer 744 * @param propertyName 745 */ 746 protected void dontBlankOutFieldsOnUpdate(Customer batchCustomer, Customer existingCustomer, String propertyName) { 747 String batchValue; 748 String existingValue; 749 Class<?> propertyClass = null; 750 751 // try to retrieve the property type to see if it exists at all 752 try { 753 propertyClass = PropertyUtils.getPropertyType(batchCustomer, propertyName); 754 } 755 catch (Exception e) { 756 throw new RuntimeException("Could not access properties on the Customer object.", e); 757 } 758 759 // if the property doesnt exist, then throw an exception 760 if (propertyClass == null) { 761 throw new IllegalArgumentException("The propertyName specified [" + propertyName + "] doesnt exist on the Customer object."); 762 } 763 764 // get the String values of both batch and existing, to compare 765 try { 766 batchValue = BeanUtils.getSimpleProperty(batchCustomer, propertyName); 767 existingValue = BeanUtils.getSimpleProperty(existingCustomer, propertyName); 768 } 769 catch (Exception e) { 770 throw new RuntimeException("Could not access properties on the Customer object.", e); 771 } 772 773 // if the existing is non-blank, and the new is blank, then over-write the new with the existing value 774 if (StringUtils.isBlank(batchValue) && StringUtils.isNotBlank(existingValue)) { 775 776 // get the real typed value, and then try to set the property value 777 try { 778 Object typedValue = PropertyUtils.getProperty(existingCustomer, propertyName); 779 BeanUtils.setProperty(batchCustomer, propertyName, typedValue); 780 } 781 catch (Exception e) { 782 throw new RuntimeException("Could not set properties on the Customer object.", e); 783 } 784 } 785 } 786 787 protected boolean validateSingle(MaintenanceDocument maintDoc, CustomerLoadBatchErrors batchErrors, String customerName) { 788 boolean result = true; 789 790 // get an instance of the business rule 791 CustomerRule rule = new CustomerRule(); 792 793 // run the business rules 794 result &= rule.processRouteDocument(maintDoc); 795 796 extractGlobalVariableErrors(batchErrors, customerName); 797 798 return result; 799 } 800 801 protected boolean extractGlobalVariableErrors(CustomerLoadBatchErrors batchErrors, String customerName) { 802 boolean result = true; 803 804 MessageMap errorMap = GlobalVariables.getMessageMap(); 805 806 Set<String> errorKeys = errorMap.keySet(); 807 List<ErrorMessage> errorMessages = null; 808 Object[] messageParams; 809 String errorKeyString; 810 String errorString; 811 812 for (String errorProperty : errorKeys) { 813 errorMessages = (List<ErrorMessage>) errorMap.get(errorProperty); 814 for (ErrorMessage errorMessage : errorMessages) { 815 errorKeyString = configService.getPropertyString(errorMessage.getErrorKey()); 816 messageParams = errorMessage.getMessageParameters(); 817 818 // MessageFormat.format only seems to replace one 819 // per pass, so I just keep beating on it until all are gone. 820 if (StringUtils.isBlank(errorKeyString)) { 821 errorString = errorMessage.getErrorKey(); 822 } 823 else { 824 errorString = errorKeyString; 825 } 826 while (errorString.matches("^.*\\{\\d\\}.*$")) { 827 errorString = MessageFormat.format(errorString, messageParams); 828 } 829 batchErrors.addError(customerName, errorProperty, Object.class, "", errorString); 830 result = false; 831 } 832 } 833 834 // clear the stuff out of globalvars, as we need to reformat it and put it back 835 GlobalVariables.getMessageMap().clear(); 836 return result; 837 } 838 839 protected MaintenanceDocument createTransientMaintDoc() { 840 MaintenanceDocument maintDoc = new MaintenanceDocumentBase(getCustomerMaintenanceDocumentTypeName()); 841 return maintDoc; 842 } 843 844 protected MaintenanceDocument createRealMaintDoc(MaintenanceDocument document) { 845 if (document == null) { 846 try { 847 document = (MaintenanceDocument) docService.getNewDocument(getCustomerMaintenanceDocumentTypeName()); 848 } 849 catch (WorkflowException e) { 850 throw new RuntimeException("WorkflowException thrown when trying to create new MaintenanceDocument.", e); 851 } 852 } 853 return document; 854 } 855 856 /** 857 */ 858 protected Customer customerAlreadyExists(Customer customer) { 859 860 Customer existingCustomer = null; 861 862 // test existence by customerNumber, if one is passed in 863 if (StringUtils.isNotBlank(customer.getCustomerNumber())) { 864 existingCustomer = customerService.getByPrimaryKey(customer.getCustomerNumber()); 865 if (existingCustomer != null) { 866 return existingCustomer; 867 } 868 } 869 870 // test existence by TaxNumber, if one is passed in 871 if (StringUtils.isNotBlank(customer.getCustomerTaxNbr())) { 872 existingCustomer = customerService.getByTaxNumber(customer.getCustomerTaxNbr()); 873 if (existingCustomer != null) { 874 return existingCustomer; 875 } 876 } 877 878 // test existence by Customer Name. this is looking for an exact match, so isnt terribly effective 879 if (StringUtils.isNotBlank(customer.getCustomerName())) { 880 existingCustomer = customerService.getCustomerByName(customer.getCustomerName()); 881 if (existingCustomer != null) { 882 return existingCustomer; 883 } 884 } 885 886 // return a null Customer if no matches were found 887 return existingCustomer; 888 } 889 890 protected void writeReportPDF(List<CustomerLoadFileResult> fileResults) { 891 892 if (fileResults.isEmpty()) { 893 return; 894 } 895 896 // setup the PDF business 897 Document pdfDoc = new Document(PageSize.LETTER, 54, 54, 72, 72); 898 getPdfWriter(pdfDoc); 899 pdfDoc.open(); 900 901 if (fileResults.isEmpty()) { 902 writeFileNameSectionTitle(pdfDoc, "NO DOCUMENTS FOUND TO PROCESS"); 903 return; 904 } 905 906 CustomerLoadResult result; 907 String customerResultLine; 908 for (CustomerLoadFileResult fileResult : fileResults) { 909 910 // file name title 911 String fileNameOnly = fileResult.getFilename().toUpperCase(); 912 fileNameOnly = fileNameOnly.substring(fileNameOnly.lastIndexOf("\\") + 1); 913 writeFileNameSectionTitle(pdfDoc, fileNameOnly); 914 915 // write any file-general messages 916 writeMessageEntryLines(pdfDoc, fileResult.getMessages()); 917 918 // walk through each customer included in this file 919 for (String customerName : fileResult.getCustomerNames()) { 920 result = fileResult.getCustomer(customerName); 921 922 // write the customer title 923 writeCustomerSectionTitle(pdfDoc, customerName.toUpperCase()); 924 925 // write a success/failure results line for this customer 926 customerResultLine = result.getResultString() + (ResultCode.SUCCESS.equals(result.getResult()) ? WORKFLOW_DOC_ID_PREFIX + result.getWorkflowDocId() : ""); 927 writeCustomerSectionResult(pdfDoc, customerResultLine); 928 929 // write any customer messages 930 writeMessageEntryLines(pdfDoc, result.getMessages()); 931 } 932 } 933 934 pdfDoc.close(); 935 } 936 937 protected void writeFileNameSectionTitle(Document pdfDoc, String filenameLine) { 938 Font font = FontFactory.getFont(FontFactory.COURIER, 10, Font.BOLD); 939 940 Paragraph paragraph = new Paragraph(); 941 paragraph.setAlignment(Element.ALIGN_LEFT); 942 Chunk chunk = new Chunk(filenameLine, font); 943 chunk.setBackground(Color.LIGHT_GRAY, 5, 5, 5, 5); 944 paragraph.add(chunk); 945 946 // blank line 947 paragraph.add(new Chunk("", font)); 948 949 try { 950 pdfDoc.add(paragraph); 951 } 952 catch (DocumentException e) { 953 LOG.error("iText DocumentException thrown when trying to write content.", e); 954 throw new RuntimeException("iText DocumentException thrown when trying to write content.", e); 955 } 956 } 957 958 protected void writeCustomerSectionTitle(Document pdfDoc, String customerNameLine) { 959 Font font = FontFactory.getFont(FontFactory.COURIER, 8, Font.BOLD + Font.UNDERLINE); 960 961 Paragraph paragraph = new Paragraph(); 962 paragraph.setAlignment(Element.ALIGN_LEFT); 963 paragraph.add(new Chunk(customerNameLine, font)); 964 965 // blank line 966 paragraph.add(new Chunk("", font)); 967 968 try { 969 pdfDoc.add(paragraph); 970 } 971 catch (DocumentException e) { 972 LOG.error("iText DocumentException thrown when trying to write content.", e); 973 throw new RuntimeException("iText DocumentException thrown when trying to write content.", e); 974 } 975 } 976 977 protected void writeCustomerSectionResult(Document pdfDoc, String resultLine) { 978 Font font = FontFactory.getFont(FontFactory.COURIER, 8, Font.BOLD); 979 980 Paragraph paragraph = new Paragraph(); 981 paragraph.setAlignment(Element.ALIGN_LEFT); 982 paragraph.add(new Chunk(resultLine, font)); 983 984 // blank line 985 paragraph.add(new Chunk("", font)); 986 987 try { 988 pdfDoc.add(paragraph); 989 } 990 catch (DocumentException e) { 991 LOG.error("iText DocumentException thrown when trying to write content.", e); 992 throw new RuntimeException("iText DocumentException thrown when trying to write content.", e); 993 } 994 } 995 996 protected void writeMessageEntryLines(Document pdfDoc, List<String[]> messageLines) { 997 Font font = FontFactory.getFont(FontFactory.COURIER, 8, Font.NORMAL); 998 999 Paragraph paragraph; 1000 String messageEntry; 1001 for (String[] messageLine : messageLines) { 1002 paragraph = new Paragraph(); 1003 paragraph.setAlignment(Element.ALIGN_LEFT); 1004 messageEntry = StringUtils.rightPad(messageLine[0], (12 - messageLine[0].length()), " ") + " - " + messageLine[1].toUpperCase(); 1005 paragraph.add(new Chunk(messageEntry, font)); 1006 1007 // blank line 1008 paragraph.add(new Chunk("", font)); 1009 1010 try { 1011 pdfDoc.add(paragraph); 1012 } 1013 catch (DocumentException e) { 1014 LOG.error("iText DocumentException thrown when trying to write content.", e); 1015 throw new RuntimeException("iText DocumentException thrown when trying to write content.", e); 1016 } 1017 } 1018 } 1019 1020 protected void getPdfWriter(Document pdfDoc) { 1021 1022 String reportDropFolder = reportsDirectory + "/" + ArConstants.CustomerLoad.CUSTOMER_LOAD_REPORT_SUBFOLDER + "/"; 1023 String fileName = ArConstants.CustomerLoad.BATCH_REPORT_BASENAME + "_" + 1024 new SimpleDateFormat("yyyyMMdd_HHmmssSSS").format(dateTimeService.getCurrentDate()) + ".pdf"; 1025 1026 // setup the writer 1027 File reportFile = new File(reportDropFolder + fileName); 1028 FileOutputStream fileOutStream; 1029 try { 1030 fileOutStream = new FileOutputStream(reportFile); 1031 } 1032 catch (IOException e) { 1033 LOG.error("IOException thrown when trying to open the FileOutputStream.", e); 1034 throw new RuntimeException("IOException thrown when trying to open the FileOutputStream.", e); 1035 } 1036 BufferedOutputStream buffOutStream = new BufferedOutputStream(fileOutStream); 1037 1038 try { 1039 PdfWriter.getInstance(pdfDoc, buffOutStream); 1040 } 1041 catch (DocumentException e) { 1042 LOG.error("iText DocumentException thrown when trying to start a new instance of the PdfWriter.", e); 1043 throw new RuntimeException("iText DocumentException thrown when trying to start a new instance of the PdfWriter.", e); 1044 } 1045 1046 } 1047 1048 public void setBatchInputFileService(BatchInputFileService batchInputFileService) { 1049 this.batchInputFileService = batchInputFileService; 1050 } 1051 1052 public void setCustomerService(CustomerService customerService) { 1053 this.customerService = customerService; 1054 } 1055 1056 public void setConfigService(KualiConfigurationService configService) { 1057 this.configService = configService; 1058 } 1059 1060 public void setDocService(DocumentService docService) { 1061 this.docService = docService; 1062 } 1063 1064 public void setBatchInputFileType(BatchInputFileType batchInputFileType) { 1065 this.batchInputFileType = batchInputFileType; 1066 } 1067 1068 public void setParameterService(ParameterService parameterService) { 1069 this.parameterService = parameterService; 1070 } 1071 1072 public void setOrgService(OrganizationService orgService) { 1073 this.orgService = orgService; 1074 } 1075 1076 public void setSysInfoService(SystemInformationService sysInfoService) { 1077 this.sysInfoService = sysInfoService; 1078 } 1079 1080 public void setBoService(BusinessObjectService boService) { 1081 this.boService = boService; 1082 } 1083 1084 public void setDateTimeService(DateTimeService dateTimeService) { 1085 this.dateTimeService = dateTimeService; 1086 } 1087 1088 public void setReportsDirectory(String reportsDirectory) { 1089 this.reportsDirectory = reportsDirectory; 1090 } 1091 1092 } 1093