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    }