/*
* #%L
* BroadleafCommerce Framework Web
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.core.web.processor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.currency.util.BroadleafCurrencyUtils;
import org.broadleafcommerce.common.money.Money;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.common.web.dialect.AbstractModelVariableModifierProcessor;
import org.broadleafcommerce.core.catalog.domain.Product;
import org.broadleafcommerce.core.catalog.domain.ProductOption;
import org.broadleafcommerce.core.catalog.domain.ProductOptionValue;
import org.broadleafcommerce.core.catalog.domain.Sku;
import org.broadleafcommerce.core.catalog.service.CatalogService;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.util.LRUMap;
import org.thymeleaf.Arguments;
import org.thymeleaf.dom.Element;
import org.thymeleaf.standard.expression.Expression;
import org.thymeleaf.standard.expression.StandardExpressions;
import java.io.StringWriter;
import java.io.Writer;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
/**
* This processor will add the following information to the model, available for consumption by a template:
* -pricing for a sku based on the product option values selected
* -the complete set of product options and values for a given product
*
* @author jfridye
*
*/
public class ProductOptionsProcessor extends AbstractModelVariableModifierProcessor {
@Resource(name = "blCatalogService")
protected CatalogService catalogService;
private static final Log LOG = LogFactory.getLog(ProductOptionsProcessor.class);
protected static final Map<Object, String> JSON_CACHE = Collections.synchronizedMap(new LRUMap<Object, String>(100, 500));
public ProductOptionsProcessor() {
super("product_options");
}
@Override
public int getPrecedence() {
return 10000;
}
@Override
protected void modifyModelAttributes(Arguments arguments, Element element) {
Expression expression = (Expression) StandardExpressions.getExpressionParser(arguments.getConfiguration())
.parseExpression(arguments.getConfiguration(), arguments, element.getAttributeValue("productId"));
Long productId = (Long) expression.execute(arguments.getConfiguration(), arguments);
Product product = catalogService.findProductById(productId);
if (product != null) {
addAllProductOptionsToModel(arguments, product);
addProductOptionPricingToModel(arguments, product);
}
}
private void addProductOptionPricingToModel(Arguments arguments, Product product) {
List<Sku> skus = product.getSkus();
List<ProductOptionPricingDTO> skuPricing = new ArrayList<ProductOptionPricingDTO>();
for (Sku sku : skus) {
List<Long> productOptionValueIds = new ArrayList<Long>();
List<ProductOptionValue> productOptionValues = sku.getProductOptionValues();
for (ProductOptionValue productOptionValue : productOptionValues) {
productOptionValueIds.add(productOptionValue.getId());
}
Long[] values = new Long[productOptionValueIds.size()];
productOptionValueIds.toArray(values);
ProductOptionPricingDTO dto = new ProductOptionPricingDTO();
Money currentPrice;
if (sku.isOnSale()) {
currentPrice = sku.getSalePrice();
} else {
currentPrice = sku.getRetailPrice();
}
dto.setPrice(formatPrice(currentPrice));
dto.setSelectedOptions(values);
skuPricing.add(dto);
}
writeJSONToModel(arguments, "skuPricing", skuPricing);
}
private void addAllProductOptionsToModel(Arguments arguments, Product product) {
List<ProductOption> productOptions = product.getProductOptions();
List<ProductOptionDTO> dtos = new ArrayList<ProductOptionDTO>();
for (ProductOption option : productOptions) {
ProductOptionDTO dto = new ProductOptionDTO();
dto.setId(option.getId());
dto.setType(option.getType().getType());
Map<Long, String> values = new HashMap<Long, String>();
for (ProductOptionValue value : option.getAllowedValues()) {
values.put(value.getId(), value.getAttributeValue());
}
dto.setValues(values);
dtos.add(dto);
}
writeJSONToModel(arguments, "allProductOptions", dtos);
}
private void writeJSONToModel(Arguments arguments, String modelKey, Object o) {
try {
if (!JSON_CACHE.containsKey(o)) {
ObjectMapper mapper = new ObjectMapper();
Writer strWriter = new StringWriter();
mapper.writeValue(strWriter, o);
JSON_CACHE.put(o, strWriter.toString());
}
addToModel(arguments, modelKey, JSON_CACHE.get(o));
} catch (Exception ex) {
LOG.error("There was a problem writing the product option map to JSON", ex);
}
}
private String formatPrice(Money price){
if (price == null){
return null;
}
BroadleafRequestContext brc = BroadleafRequestContext.getBroadleafRequestContext();
if (brc.getJavaLocale() != null) {
return BroadleafCurrencyUtils.getNumberFormatFromCache(brc.getJavaLocale(), price.getCurrency()).format
(price.getAmount());
} else {
// Setup your BLC_CURRENCY and BLC_LOCALE to display a diff default.
return "$ " + price.getAmount().toString();
}
}
private class ProductOptionDTO {
private Long id;
private String type;
private Map<Long, String> values;
private String selectedValue;
@SuppressWarnings("unused")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@SuppressWarnings("unused")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@SuppressWarnings("unused")
public Map<Long, String> getValues() {
return values;
}
public void setValues(Map<Long, String> values) {
this.values = values;
}
@SuppressWarnings("unused")
public String getSelectedValue() {
return selectedValue;
}
@SuppressWarnings("unused")
public void setSelectedValue(String selectedValue) {
this.selectedValue = selectedValue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!getClass().isAssignableFrom(o.getClass())) return false;
ProductOptionDTO that = (ProductOptionDTO) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (selectedValue != null ? !selectedValue.equals(that.selectedValue) : that.selectedValue != null)
return false;
if (type != null ? !type.equals(that.type) : that.type != null) return false;
if (values != null ? !values.equals(that.values) : that.values != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (values != null ? values.hashCode() : 0);
result = 31 * result + (selectedValue != null ? selectedValue.hashCode() : 0);
return result;
}
}
private class ProductOptionPricingDTO {
private Long[] skuOptions;
private String price;
@SuppressWarnings("unused")
public Long[] getSelectedOptions() {
return skuOptions;
}
public void setSelectedOptions(Long[] skuOptions) {
this.skuOptions = skuOptions;
}
@SuppressWarnings("unused")
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!getClass().isAssignableFrom(o.getClass())) return false;
ProductOptionPricingDTO that = (ProductOptionPricingDTO) o;
if (price != null ? !price.equals(that.price) : that.price != null) return false;
if (!Arrays.equals(skuOptions, that.skuOptions)) return false;
return true;
}
@Override
public int hashCode() {
int result = skuOptions != null ? Arrays.hashCode(skuOptions) : 0;
result = 31 * result + (price != null ? price.hashCode() : 0);
return result;
}
}
}