001/* 002 * Copyright 2014 the original author or authors. 003 * 004 * Licensed under the Apache 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 * https://www.apache.org/licenses/LICENSE-2.0 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 */ 016package org.springframework.batch.sample.domain.order.internal.validator; 017 018import java.math.BigDecimal; 019import java.util.ArrayList; 020import java.util.Date; 021import java.util.List; 022 023import org.springframework.batch.sample.domain.order.Address; 024import org.springframework.batch.sample.domain.order.BillingInfo; 025import org.springframework.batch.sample.domain.order.Customer; 026import org.springframework.batch.sample.domain.order.LineItem; 027import org.springframework.batch.sample.domain.order.Order; 028import org.springframework.batch.sample.domain.order.ShippingInfo; 029import org.springframework.util.StringUtils; 030import org.springframework.validation.Errors; 031import org.springframework.validation.Validator; 032 033public class OrderValidator implements Validator { 034 035 private static final List<String> CARD_TYPES = new ArrayList<String>(); 036 private static final List<String> SHIPPER_IDS = new ArrayList<String>(); 037 private static final List<String> SHIPPER_TYPES = new ArrayList<String>(); 038 private static final long MAX_ID = 9999999999L; 039 private static final BigDecimal BD_MIN = new BigDecimal("0.0"); 040 private static final BigDecimal BD_MAX = new BigDecimal("99999999.99"); 041 private static final BigDecimal BD_PERC_MAX = new BigDecimal("100.0"); 042 private static final int MAX_QUANTITY = 9999; 043 private static final BigDecimal BD_100 = new BigDecimal("100.00"); 044 045 static { 046 CARD_TYPES.add("VISA"); 047 CARD_TYPES.add("AMEX"); 048 CARD_TYPES.add("ECMC"); 049 CARD_TYPES.add("DCIN"); 050 CARD_TYPES.add("PAYP"); 051 052 SHIPPER_IDS.add("FEDX"); 053 SHIPPER_IDS.add("UPS"); 054 SHIPPER_IDS.add("DHL"); 055 SHIPPER_IDS.add("DPD"); 056 057 SHIPPER_TYPES.add("STD"); 058 SHIPPER_TYPES.add("EXP"); 059 SHIPPER_TYPES.add("AMS"); 060 SHIPPER_TYPES.add("AME"); 061 } 062 063 @Override 064 public boolean supports(Class<?> arg0) { 065 return arg0.isAssignableFrom(Order.class); 066 } 067 068 @Override 069 public void validate(Object arg0, Errors errors) { 070 Order item = null; 071 try { 072 item = (Order) arg0; 073 } catch (ClassCastException cce) { 074 errors.reject("Incorrect type"); 075 } 076 077 if(item != null) { 078 validateOrder(item, errors); 079 validateCustomer(item.getCustomer(), errors); 080 validateAddress(item.getBillingAddress(), errors, "billingAddress"); 081 validateAddress(item.getShippingAddress(), errors, "shippingAddress"); 082 validatePayment(item.getBilling(), errors); 083 validateShipping(item.getShipping(), errors); 084 validateLineItems(item.getLineItems(), errors); 085 } 086 } 087 088 protected void validateLineItems(List<LineItem> lineItems, Errors errors) { 089 boolean ids = true; 090 boolean prices = true; 091 boolean discounts = true; 092 boolean shippingPrices = true; 093 boolean handlingPrices = true; 094 boolean quantities = true; 095 boolean totalPrices = true; 096 097 for (LineItem lineItem : lineItems) { 098 if(lineItem.getItemId() <= 0 || lineItem.getItemId() > MAX_ID) { 099 ids = false; 100 } 101 102 if((BD_MIN.compareTo(lineItem.getPrice()) > 0) || (BD_MAX.compareTo(lineItem.getPrice()) < 0)) { 103 prices = false; 104 } 105 106 if (BD_MIN.compareTo(lineItem.getDiscountPerc()) != 0) { 107 //DiscountPerc must be between 0.0 and 100.0 108 if ((BD_MIN.compareTo(lineItem.getDiscountPerc()) > 0) 109 || (BD_PERC_MAX.compareTo(lineItem.getDiscountPerc()) < 0) 110 || (BD_MIN.compareTo(lineItem.getDiscountAmount()) != 0)) { //only one of DiscountAmount and DiscountPerc should be non-zero 111 discounts = false; 112 } 113 } else { 114 //DiscountAmount must be between 0.0 and item.price 115 if ((BD_MIN.compareTo(lineItem.getDiscountAmount()) > 0) 116 || (lineItem.getPrice().compareTo(lineItem.getDiscountAmount()) < 0)) { 117 discounts = false; 118 } 119 } 120 121 if ((BD_MIN.compareTo(lineItem.getShippingPrice()) > 0) || (BD_MAX.compareTo(lineItem.getShippingPrice()) < 0)) { 122 shippingPrices = false; 123 } 124 125 if ((BD_MIN.compareTo(lineItem.getHandlingPrice()) > 0) || (BD_MAX.compareTo(lineItem.getHandlingPrice()) < 0)) { 126 handlingPrices = false; 127 } 128 129 if ((lineItem.getQuantity() <= 0) || (lineItem.getQuantity() > MAX_QUANTITY)) { 130 quantities = false; 131 } 132 133 134 if ((BD_MIN.compareTo(lineItem.getTotalPrice()) > 0) 135 || (BD_MAX.compareTo(lineItem.getTotalPrice()) < 0)) { 136 totalPrices = false; 137 } 138 139 //calculate total price 140 141 //discount coefficient = (100.00 - discountPerc) / 100.00 142 BigDecimal coef = BD_100.subtract(lineItem.getDiscountPerc()) 143 .divide(BD_100, 4, BigDecimal.ROUND_HALF_UP); 144 145 //discountedPrice = (price * coefficient) - discountAmount 146 //at least one of discountPerc and discountAmount is 0 - this is validated by ValidateDiscountsFunction 147 BigDecimal discountedPrice = lineItem.getPrice().multiply(coef) 148 .subtract(lineItem.getDiscountAmount()); 149 150 //price for single item = discountedPrice + shipping + handling 151 BigDecimal singleItemPrice = discountedPrice.add(lineItem.getShippingPrice()) 152 .add(lineItem.getHandlingPrice()); 153 154 //total price = singleItemPrice * quantity 155 BigDecimal quantity = new BigDecimal(lineItem.getQuantity()); 156 BigDecimal totalPrice = singleItemPrice.multiply(quantity) 157 .setScale(2, BigDecimal.ROUND_HALF_UP); 158 159 //calculatedPrice should equal to item.totalPrice 160 if (totalPrice.compareTo(lineItem.getTotalPrice()) != 0) { 161 totalPrices = false; 162 } 163 } 164 165 String lineItemsFieldName = "lineItems"; 166 167 if(!ids) { 168 errors.rejectValue(lineItemsFieldName, "error.lineitems.id"); 169 } 170 171 if(!prices) { 172 errors.rejectValue(lineItemsFieldName, "error.lineitems.price"); 173 } 174 175 if(!discounts) { 176 errors.rejectValue(lineItemsFieldName, "error.lineitems.discount"); 177 } 178 179 if(!shippingPrices) { 180 errors.rejectValue(lineItemsFieldName, "error.lineitems.shipping"); 181 } 182 183 if(!handlingPrices) { 184 errors.rejectValue(lineItemsFieldName, "error.lineitems.handling"); 185 } 186 187 if(!quantities) { 188 errors.rejectValue(lineItemsFieldName, "error.lineitems.quantity"); 189 } 190 191 if(!totalPrices) { 192 errors.rejectValue(lineItemsFieldName, "error.lineitems.totalprice"); 193 } 194 } 195 196 protected void validateShipping(ShippingInfo shipping, Errors errors) { 197 if(!SHIPPER_IDS.contains(shipping.getShipperId())) { 198 errors.rejectValue("shipping.shipperId", "error.shipping.shipper"); 199 } 200 201 if(!SHIPPER_TYPES.contains(shipping.getShippingTypeId())) { 202 errors.rejectValue("shipping.shippingTypeId", "error.shipping.type"); 203 } 204 205 if(StringUtils.hasText(shipping.getShippingInfo())) { 206 validateStringLength(shipping.getShippingInfo(), errors, "shipping.shippingInfo", "error.shipping.shippinginfo.length", 100); 207 } 208 } 209 210 protected void validatePayment(BillingInfo billing, Errors errors) { 211 if(!CARD_TYPES.contains(billing.getPaymentId())) { 212 errors.rejectValue("billing.paymentId", "error.billing.type"); 213 } 214 215 if(!billing.getPaymentDesc().matches("[A-Z]{4}-[0-9]{10,11}")) { 216 errors.rejectValue("billing.paymentDesc", "error.billing.desc"); 217 } 218 } 219 220 protected void validateAddress(Address address, Errors errors, 221 String prefix) { 222 if(address != null) { 223 if(StringUtils.hasText(address.getAddressee())) { 224 validateStringLength(address.getAddressee(), errors, prefix + ".addressee", "error.baddress.addresse.length", 60); 225 } 226 227 validateStringLength(address.getAddrLine1(), errors, prefix + ".addrLine1", "error.baddress.addrline1.length", 50); 228 229 if(StringUtils.hasText(address.getAddrLine2())) { 230 validateStringLength(address.getAddrLine2(), errors, prefix + ".addrLine2", "error.baddress.addrline2.length", 50); 231 } 232 validateStringLength(address.getCity(), errors, prefix + ".city", "error.baddress.city.length", 30); 233 validateStringLength(address.getZipCode(), errors, prefix + ".zipCode", "error.baddress.zipcode.length", 5); 234 235 if(StringUtils.hasText(address.getZipCode()) && !address.getZipCode().matches("[0-9]{5}")) { 236 errors.rejectValue(prefix + ".zipCode", "error.baddress.zipcode.format"); 237 } 238 239 if((!StringUtils.hasText(address.getState()) && ("United States".equals(address.getCountry())) || StringUtils.hasText(address.getState()) && address.getState().length() != 2)) { 240 errors.rejectValue(prefix + ".state", "error.baddress.state.length"); 241 } 242 243 validateStringLength(address.getCountry(), errors, prefix + ".country", "error.baddress.country.length", 50); 244 } 245 } 246 247 protected void validateStringLength(String string, Errors errors, 248 String field, String message, int length) { 249 if(!StringUtils.hasText(string) || string.length() > length) { 250 errors.rejectValue(field, message); 251 } 252 } 253 254 protected void validateCustomer(Customer customer, Errors errors) { 255 if(!customer.isRegistered() && customer.isBusinessCustomer()) { 256 errors.rejectValue("customer.registered", "error.customer.registration"); 257 } 258 259 if(!StringUtils.hasText(customer.getCompanyName()) && customer.isBusinessCustomer()) { 260 errors.rejectValue("customer.companyName", "error.customer.companyname"); 261 } 262 263 if(!StringUtils.hasText(customer.getFirstName()) && !customer.isBusinessCustomer()) { 264 errors.rejectValue("customer.firstName", "error.customer.firstname"); 265 } 266 267 if(!StringUtils.hasText(customer.getLastName()) && !customer.isBusinessCustomer()) { 268 errors.rejectValue("customer.lastName", "error.customer.lastname"); 269 } 270 271 if(customer.isRegistered() && (customer.getRegistrationId() < 0 || customer.getRegistrationId() >= 99999999L)) { 272 errors.rejectValue("customer.registrationId", "error.customer.registrationid"); 273 } 274 } 275 276 protected void validateOrder(Order item, Errors errors) { 277 if(item.getOrderId() < 0 || item.getOrderId() > 9999999999L) { 278 errors.rejectValue("orderId", "error.order.id"); 279 } 280 281 if(new Date().compareTo(item.getOrderDate()) < 0) { 282 errors.rejectValue("orderDate", "error.order.date.future"); 283 } 284 285 if(item.getLineItems() != null && item.getTotalLines() != item.getLineItems().size()) { 286 errors.rejectValue("totalLines", "error.order.lines.badcount"); 287 } 288 } 289}