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.vo; 017 018 import org.apache.commons.beanutils.ConversionException; 019 import org.apache.commons.beanutils.converters.SqlDateConverter; 020 import org.apache.commons.lang.StringUtils; 021 import org.kuali.kfs.module.ar.batch.report.CustomerLoadBatchErrors; 022 import org.kuali.kfs.module.ar.businessobject.Customer; 023 import org.kuali.kfs.module.ar.businessobject.CustomerAddress; 024 import org.kuali.kfs.sys.context.SpringContext; 025 import org.kuali.rice.kns.service.DateTimeService; 026 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; 027 import org.kuali.rice.kns.util.KualiDecimal; 028 029 /** 030 * 031 * This class converts a CustomerDigesterVO object to a standard 032 * Customer object. 033 */ 034 public class CustomerDigesterAdapter { 035 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CustomerDigesterAdapter.class); 036 037 private static final Class<Customer> BO_CLASS = Customer.class; 038 private static final String DD_ENTRY_NAME = BO_CLASS.getName(); 039 040 private DateTimeService dateTimeService; 041 private MaintenanceDocumentDictionaryService maintDocDDService; 042 043 private Customer customer; 044 private String customerName; 045 private CustomerLoadBatchErrors errors; 046 private CustomerDigesterVO customerDigesterVO; 047 048 public CustomerDigesterAdapter() { 049 if (dateTimeService == null) dateTimeService = SpringContext.getBean(DateTimeService.class); 050 if (maintDocDDService == null) maintDocDDService = SpringContext.getBean(MaintenanceDocumentDictionaryService.class); 051 } 052 053 /** 054 * 055 * Converts a CustomerDigesterVO to a real Customer BO. Tries to do intelligent type 056 * conversions where the types arent Strings. 057 * 058 * NOTE that conversion exceptions will be swallowed! and converted to errors in the 059 * parameter errorMap. 060 * 061 * @param customerDigesterVO The VO full of String values to convert from. 062 * @param errorMap An empty ErrorMap collection to add errors to. Only new errors will be added. 063 * @return A populated Customer object, from the VO. 064 */ 065 public Customer convert(CustomerDigesterVO customerDigesterVO, CustomerLoadBatchErrors errors) { 066 067 if (customerDigesterVO == null) { 068 throw new IllegalArgumentException("Parameter customerDigesterVO may not be null."); 069 } 070 this.customerDigesterVO = customerDigesterVO; 071 072 // the whole error system is keyed of customerName, so if we dont get one of those, we cant even proceed. 073 if (StringUtils.isBlank(customerDigesterVO.getCustomerName())) { 074 LOG.error("CustomerName can never be empty-string or null."); 075 addError("customerName", String.class, customerDigesterVO.getCustomerName(), "CustomerName can never be empty-string or null."); 076 return null; 077 } 078 079 customer = new Customer(); 080 customerName = this.customerDigesterVO.getCustomerName(); 081 082 if (errors == null) { 083 LOG.error("Passed in CustomerLoadBatchErrors must not be null."); 084 throw new IllegalArgumentException("Passed in CustomerLoadBatchErrors must not be null."); 085 } 086 this.errors = errors; 087 088 convertCustomerStringProperties(); 089 convertCustomerDateProperties(); 090 convertCustomerKualiDecimalProperties(); 091 convertCustomerBooleanProperties(); 092 convertCustomerAddresses(); 093 094 return customer; 095 } 096 097 private void convertCustomerStringProperties() { 098 099 // these are String to String conversions, so they will always work 100 customer.setCustomerNumber(applyDefaultValue("customerNumber", customerDigesterVO.getCustomerNumber())); 101 customer.setCustomerName(applyDefaultValue("customerName", customerDigesterVO.getCustomerName())); 102 customer.setCustomerParentCompanyNumber(applyDefaultValue("customerParentCompanyNumber", customerDigesterVO.getCustomerParentCompanyNumber())); 103 customer.setCustomerTypeCode(applyDefaultValue("customerTypeCode", customerDigesterVO.getCustomerTypeCode())); 104 customer.setCustomerTaxTypeCode(applyDefaultValue("customerTaxTypeCode", customerDigesterVO.getCustomerTaxTypeCode())); 105 customer.setCustomerTaxNbr(applyDefaultValue("customerTaxNbr", customerDigesterVO.getCustomerTaxNbr())); 106 customer.setCustomerPhoneNumber(applyDefaultValue("customerPhoneNumber", customerDigesterVO.getCustomerPhoneNumber())); 107 customer.setCustomer800PhoneNumber(applyDefaultValue("customer800PhoneNumber", customerDigesterVO.getCustomer800PhoneNumber())); 108 customer.setCustomerContactName(applyDefaultValue("customerContactName", customerDigesterVO.getCustomerContactName())); 109 customer.setCustomerContactPhoneNumber(applyDefaultValue("customerContactPhoneNumber", customerDigesterVO.getCustomerContactPhoneNumber())); 110 customer.setCustomerFaxNumber(applyDefaultValue("customerFaxNumber", customerDigesterVO.getCustomerFaxNumber())); 111 customer.setCustomerCreditApprovedByName(applyDefaultValue("customerCreditApprovedByName", customerDigesterVO.getCustomerCreditApprovedByName())); 112 customer.setCustomerEmailAddress(applyDefaultValue("customerEmailAddress", customerDigesterVO.getCustomerEmailAddress())); 113 return; 114 } 115 116 private void convertCustomerDateProperties() { 117 java.sql.Date todayDate = dateTimeService.getCurrentSqlDate(); 118 119 customer.setCustomerAddressChangeDate(todayDate); 120 customer.setCustomerRecordAddDate(todayDate); 121 customer.setCustomerLastActivityDate(todayDate); 122 123 customer.setCustomerBirthDate(convertToJavaSqlDate("customerBirthDate", applyDefaultValue("customerBirthDate", customerDigesterVO.getCustomerBirthDate()))); 124 return; 125 } 126 127 private void convertCustomerKualiDecimalProperties() { 128 customer.setCustomerCreditLimitAmount(convertToKualiDecimal("customerCreditLimitAmount", applyDefaultValue("customerCreditLimitAmount", customerDigesterVO.getCustomerCreditLimitAmount()))); 129 return; 130 } 131 132 private void convertCustomerBooleanProperties() { 133 134 customer.setActive(convertToLittleBoolean("customerActiveIndicator", applyDefaultValue("customerActiveIndicator", customerDigesterVO.getCustomerActiveIndicator()))); 135 customer.setCustomerTaxExemptIndicator(convertToLittleBoolean("customerTaxExemptIndicator", applyDefaultValue("customerTaxExemptIndicator", customerDigesterVO.getCustomerTaxExemptIndicator()))); 136 return; 137 } 138 139 private void convertCustomerAddresses() { 140 CustomerAddress customerAddress = null; 141 for (CustomerAddressDigesterVO addressVO : customerDigesterVO.getCustomerAddresses()) { 142 customerAddress = convertCustomerAddress(addressVO, customer.getCustomerNumber()); 143 customer.getCustomerAddresses().add(customerAddress); 144 } 145 return; 146 } 147 148 private CustomerAddress convertCustomerAddress(CustomerAddressDigesterVO customerAddressDigesterVO, String customerNumber) { 149 CustomerAddress customerAddress = new CustomerAddress(); 150 151 // link the customerAddress to the parent customer 152 customerAddress.setCustomerNumber(customerNumber); 153 154 customerAddress.setCustomerAddressName(applyDefaultValue("customerAddressName", customerAddressDigesterVO.getCustomerAddressName())); 155 customerAddress.setCustomerLine1StreetAddress(applyDefaultValue("customerLine1StreetAddress", customerAddressDigesterVO.getCustomerLine1StreetAddress())); 156 customerAddress.setCustomerLine2StreetAddress(applyDefaultValue("customerLine2StreetAddress", customerAddressDigesterVO.getCustomerLine2StreetAddress())); 157 customerAddress.setCustomerCityName(applyDefaultValue("customerCityName", customerAddressDigesterVO.getCustomerCityName())); 158 customerAddress.setCustomerStateCode(applyDefaultValue("customerStateCode", customerAddressDigesterVO.getCustomerStateCode())); 159 customerAddress.setCustomerZipCode(applyDefaultValue("customerZipCode", customerAddressDigesterVO.getCustomerZipCode())); 160 customerAddress.setCustomerCountryCode(applyDefaultValue("customerCountryCode", customerAddressDigesterVO.getCustomerCountryCode())); 161 customerAddress.setCustomerAddressInternationalProvinceName(applyDefaultValue("customerAddressInternationalProvinceName", customerAddressDigesterVO.getCustomerAddressInternationalProvinceName())); 162 customerAddress.setCustomerInternationalMailCode(applyDefaultValue("customerInternationalMailCode", customerAddressDigesterVO.getCustomerInternationalMailCode())); 163 customerAddress.setCustomerEmailAddress(applyDefaultValue("customerEmailAddress", customerAddressDigesterVO.getCustomerEmailAddress())); 164 customerAddress.setCustomerAddressTypeCode(applyDefaultValue("customerAddressTypeCode", customerAddressDigesterVO.getCustomerAddressTypeCode())); 165 166 customerAddress.setCustomerAddressEndDate(convertToJavaSqlDate("customerAddressEndDate", applyDefaultValue("customerAddressEndDate", customerAddressDigesterVO.getCustomerAddressEndDate()))); 167 168 return customerAddress; 169 } 170 171 /** 172 * 173 * This method converts a string value that may represent a date into a java.sql.Date. 174 * If the value is blank (whitespace, empty, null) then a null Date object is returned. If 175 * the value cannot be converted to a java.sql.Date, then a RuntimException or ConversionException 176 * will be thrown. 177 * 178 * @param propertyName Name of the field whose value is being converted. 179 * @param dateValue The value being converted. 180 * @param errorMap The errorMap to add conversion errors to. 181 * @return A valid java.sql.Date with the converted value, if possible. 182 */ 183 private java.sql.Date convertToJavaSqlDate(String propertyName, String dateValue) { 184 185 if (StringUtils.isBlank(dateValue)) { 186 return null; 187 } 188 189 java.sql.Date date = null; 190 SqlDateConverter converter = new SqlDateConverter(); 191 Object obj = null; 192 try { 193 obj = converter.convert(java.sql.Date.class, dateValue); 194 } 195 catch (ConversionException e) { 196 LOG.error("Failed to convert the value [" + dateValue + "] from field [" + propertyName + "] to a java.sql.Date."); 197 addError(propertyName, java.sql.Date.class, dateValue, "Could not convert value to target type."); 198 return null; 199 } 200 try { 201 date = (java.sql.Date) obj; 202 } 203 catch (Exception e) { 204 LOG.error("Failed to cast the converters results to a java.sql.Date."); 205 addError(propertyName, java.sql.Date.class, dateValue, "Could not convert value to target type."); 206 return null; 207 } 208 209 if (!(obj instanceof java.sql.Date)) { 210 LOG.error("Failed to convert the value [" + dateValue + "] from field [" + propertyName + "] to a java.sql.Date."); 211 addError(propertyName, java.sql.Date.class, dateValue, "Could not convert value to target type."); 212 return null; 213 } 214 return date; 215 } 216 217 /** 218 * 219 * This method converts a string, which may be blank, null or whitespace, into a KualiDecimal, if possible. 220 * 221 * A null, blank, or whitespace value passed in will result in a Null KualiDecimal value returned. A value passed in 222 * which is not blank, but cannot otherwise be converted to a KualiDecimal, will throw a ValueObjectConverterException. 223 * Otherwise, the value will be converted to a KualiDecimal and returned. 224 * 225 * @param propertyName The name of the property being converted (used for exception handling). 226 * @param stringDecimal The value being passed in, which will be converted to a KualiDecimal. 227 * @param errorMap The errorMap to add conversion errors to. 228 * @return A valid KualiDecimal value. If the method returns a value, then it will be a legitimate value. 229 */ 230 private KualiDecimal convertToKualiDecimal(String propertyName, String stringDecimal) { 231 if (StringUtils.isBlank(stringDecimal)) { 232 return null; 233 } 234 KualiDecimal kualiDecimal = null; 235 try { 236 kualiDecimal = new KualiDecimal(stringDecimal); 237 } 238 catch (NumberFormatException e) { 239 LOG.error("Failed to convert the value [" + stringDecimal + "] from field [" + propertyName + "] to a KualiDecimal."); 240 addError(propertyName, KualiDecimal.class, stringDecimal, "Could not convert value to target type."); 241 return null; 242 } 243 return kualiDecimal; 244 } 245 246 /** 247 * 248 * This method converts a String into a boolean. 249 * 250 * It will return 251 * @param propertyName 252 * @param stringBoolean 253 * @param errorMap The errorMap to add conversion errors to. 254 * @return 255 */ 256 private static final boolean convertToLittleBoolean(String propertyName, String stringBoolean) { 257 if (StringUtils.isBlank(stringBoolean)) { 258 return false; 259 } 260 if ("Y".equalsIgnoreCase(stringBoolean)) { 261 return true; 262 } 263 if ("YES".equalsIgnoreCase(stringBoolean)) { 264 return true; 265 } 266 if ("TRUE".equalsIgnoreCase(stringBoolean)) { 267 return true; 268 } 269 if ("T".equalsIgnoreCase(stringBoolean)) { 270 return true; 271 } 272 if ("1".equalsIgnoreCase(stringBoolean)) { 273 return true; 274 } 275 return false; 276 } 277 278 /** 279 * 280 * This method is used to apply any DataDictionary default value rules, if appropriate. 281 * 282 * If the incoming value isnt blank, empty, or null, then nothing happens, and the method 283 * returns the incoming value. 284 * 285 * If the value is empty/blank/null, then the MaintenanceDocumentDictionaryService is consulted, 286 * and if there is a defaultValue applied to this field, then its used in place of the empty/blank/null. 287 * 288 * @param propertyName The propertyName of the field (must match case exactly to work). 289 * @param batchValue The invoming value from the batch file, which at this point is always still a string. 290 * @return If the original value is null/blank/empty, then if a default value is configured, that default value 291 * is returned. If no default value is configured, then the original value (trimmed) is returned. Otherwise, 292 * if the original incoming value is not empty/null/blank, then the original value is immediately returned. 293 */ 294 private String applyDefaultValue(String propertyName, String batchValue) { 295 296 // short-circuit if value isnt empty/blank/null, as default wouldnt apply anyway 297 if (StringUtils.isNotBlank(batchValue)) { 298 return batchValue; 299 } 300 301 // if its a string, we try to normalize it to null if empty/blank 302 String incomingValue; 303 if (StringUtils.isBlank(batchValue)) { 304 incomingValue = null; 305 } 306 else { 307 incomingValue = StringUtils.trimToNull(batchValue); 308 } 309 310 // apply the default value from DD if exists 311 String defaultValue = maintDocDDService.getFieldDefaultValue(BO_CLASS, propertyName); 312 if (incomingValue == null && StringUtils.isNotBlank(defaultValue)) { 313 LOG.info("Applied DD default value of '" + defaultValue + "' to field [" + propertyName + "]."); 314 return defaultValue; 315 } 316 else { 317 return ((incomingValue == null) ? "" : incomingValue); 318 } 319 } 320 321 private void addError(String propertyName, Class<?> propertyClass, String origValue, String description) { 322 LOG.error("Failed conversion on field [" + propertyName + "] with value: '" + origValue + "': " + description); 323 errors.addError(customerName, propertyName, propertyClass, origValue, description); 324 } 325 326 public void setDateTimeService(DateTimeService dateTimeService) { 327 this.dateTimeService = dateTimeService; 328 } 329 330 public void setMaintDocDDService(MaintenanceDocumentDictionaryService maintDocDDService) { 331 this.maintDocDDService = maintDocDDService; 332 } 333 334 }