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 }