blob: 00d3f76354ac71de494b261f2759a962def8d0c6 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*******************************************************************************/
package org.ofbiz.product.config;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javolution.util.FastList;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilHttp;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.util.EntityQuery;
import org.ofbiz.product.catalog.CatalogWorker;
import org.ofbiz.product.config.ProductConfigWrapper.ConfigItem;
import org.ofbiz.product.config.ProductConfigWrapper.ConfigOption;
import org.ofbiz.product.product.ProductWorker;
import org.ofbiz.product.store.ProductStoreWorker;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.webapp.website.WebSiteWorker;
import org.ofbiz.base.util.cache.Cache;
import org.ofbiz.base.util.cache.UtilCache;
/**
* Product Config Worker class to reduce code in templates.
*/
public class ProductConfigWorker {
public static final String module = ProductConfigWorker.class.getName();
public static final String resource = "ProductUiLabels";
public static final String SEPARATOR = "::"; // cache key separator
private static final Cache<String, ProductConfigWrapper> productConfigCache = UtilCache.createUtilCache("product.config", true); // use soft reference to free up memory if needed
public static ProductConfigWrapper getProductConfigWrapper(String productId, String currencyUomId, HttpServletRequest request) {
ProductConfigWrapper configWrapper = null;
String catalogId = CatalogWorker.getCurrentCatalogId(request);
String webSiteId = WebSiteWorker.getWebSiteId(request);
String productStoreId = ProductStoreWorker.getProductStoreId(request);
GenericValue autoUserLogin = (GenericValue)request.getSession().getAttribute("autoUserLogin");
try {
/* caching: there is one cache created, "product.config" Each product's config wrapper is cached with a key of
* productId::catalogId::webSiteId::currencyUomId, or whatever the SEPARATOR is defined above to be.
*/
String cacheKey = productId + SEPARATOR + productStoreId + SEPARATOR + catalogId + SEPARATOR + webSiteId + SEPARATOR + currencyUomId;
configWrapper = productConfigCache.get(cacheKey);
if (configWrapper == null) {
configWrapper = new ProductConfigWrapper((Delegator)request.getAttribute("delegator"),
(LocalDispatcher)request.getAttribute("dispatcher"),
productId, productStoreId, catalogId, webSiteId,
currencyUomId, UtilHttp.getLocale(request),
autoUserLogin);
configWrapper = productConfigCache.putIfAbsentAndGet(cacheKey, new ProductConfigWrapper(configWrapper));
} else {
configWrapper = new ProductConfigWrapper(configWrapper);
}
} catch (ProductConfigWrapperException we) {
configWrapper = null;
} catch (Exception e) {
Debug.logWarning(e.getMessage(), module);
}
return configWrapper;
}
public static void fillProductConfigWrapper(ProductConfigWrapper configWrapper, HttpServletRequest request) {
int numOfQuestions = configWrapper.getQuestions().size();
for (int k = 0; k < numOfQuestions; k++) {
String[] opts = request.getParameterValues(Integer.toString(k));
if (opts == null) {
// check for standard item comments
ProductConfigWrapper.ConfigItem question = configWrapper.getQuestions().get(k);
if (question.isStandard()) {
int i = 0;
while (i <= (question.getOptions().size() -1)) {
String comments = request.getParameter("comments_" + k + "_" + i);
if (UtilValidate.isNotEmpty(comments)) {
try {
configWrapper.setSelected(k, i, comments);
} catch (Exception e) {
Debug.logWarning(e.getMessage(), module);
}
}
i++;
}
}
continue;
}
for (String opt: opts) {
int cnt = -1;
try {
cnt = Integer.parseInt(opt);
String comments = null;
ProductConfigWrapper.ConfigItem question = configWrapper.getQuestions().get(k);
if (question.isSingleChoice()) {
comments = request.getParameter("comments_" + k + "_" + "0");
} else {
comments = request.getParameter("comments_" + k + "_" + cnt);
}
configWrapper.setSelected(k, cnt, comments);
ProductConfigWrapper.ConfigOption option = configWrapper.getItemOtion(k, cnt);
// set selected variant products
if (UtilValidate.isNotEmpty(option) && (option.hasVirtualComponent())) {
List<GenericValue> components = option.getComponents();
int variantIndex = 0;
for (int i = 0; i < components.size(); i++) {
GenericValue component = components.get(i);
if (option.isVirtualComponent(component)) {
String productParamName = "add_product_id" + k + "_" + cnt + "_" + variantIndex;
String selectedProductId = request.getParameter(productParamName);
if (UtilValidate.isEmpty(selectedProductId)) {
Debug.logWarning("ERROR: Request param [" + productParamName + "] not found!", module);
} else {
// handle also feature tree virtual variant methods
if (ProductWorker.isVirtual((Delegator)request.getAttribute("delegator"), selectedProductId)) {
if ("VV_FEATURETREE".equals(ProductWorker.getProductVirtualVariantMethod((Delegator)request.getAttribute("delegator"), selectedProductId))) {
// get the selected features
List<String> selectedFeatures = FastList.newInstance();
Enumeration<String> paramNames = UtilGenerics.cast(request.getParameterNames());
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
if (paramName.startsWith("FT" + k + "_" + cnt + "_" + variantIndex)) {
selectedFeatures.add(request.getParameterValues(paramName)[0]);
}
}
// check if features are selected
if (UtilValidate.isEmpty(selectedFeatures)) {
Debug.logWarning("ERROR: No features selected for productId [" + selectedProductId+ "]", module);
}
String variantProductId = ProductWorker.getVariantFromFeatureTree(selectedProductId, selectedFeatures, (Delegator)request.getAttribute("delegator"));
if (UtilValidate.isNotEmpty(variantProductId)) {
selectedProductId = variantProductId;
} else {
Debug.logWarning("ERROR: Variant product not found!", module);
request.setAttribute("_EVENT_MESSAGE_", UtilProperties.getMessage("OrderErrorUiLabels", "cart.addToCart.incompatibilityVariantFeature", UtilHttp.getLocale(request)));
}
}
}
configWrapper.setSelected(k, cnt, i, selectedProductId);
}
variantIndex ++;
}
}
}
} catch (Exception e) {
Debug.logWarning(e.getMessage(), module);
}
}
}
}
/**
* First search persisted configurations and update configWrapper.configId if found.
* Otherwise store ProductConfigWrapper to ProductConfigConfig entity and updates configWrapper.configId with new configId
* This method persists only the selected options, price data is lost.
* @param configWrapper the ProductConfigWrapper object
* @param delegator the delegator
*/
public static void storeProductConfigWrapper(ProductConfigWrapper configWrapper, Delegator delegator) {
if (configWrapper == null || (!configWrapper.isCompleted())) return;
String configId = null;
List<ConfigItem> questions = configWrapper.getQuestions();
List<GenericValue> configsToCheck = FastList.newInstance();
int selectedOptionSize = 0;
for (ConfigItem ci: questions) {
String configItemId = null;
Long sequenceNum = null;
List<ProductConfigWrapper.ConfigOption> selectedOptions = FastList.newInstance();
List<ConfigOption> options = ci.getOptions();
if (ci.isStandard()) {
selectedOptions.addAll(options);
} else {
for (ConfigOption oneOption: options) {
if (oneOption.isSelected()) {
selectedOptions.add(oneOption);
}
}
}
if (selectedOptions.size() > 0) {
selectedOptionSize += selectedOptions.size();
configItemId = ci.getConfigItemAssoc().getString("configItemId");
sequenceNum = ci.getConfigItemAssoc().getLong("sequenceNum");
try {
List<GenericValue> configs = EntityQuery.use(delegator).from("ProductConfigConfig").where("configItemId",configItemId,"sequenceNum", sequenceNum).queryList();
for (GenericValue productConfigConfig: configs) {
for (ConfigOption oneOption: selectedOptions) {
String configOptionId = oneOption.configOption.getString("configOptionId");
if (productConfigConfig.getString("configOptionId").equals(configOptionId)) {
String comments = oneOption.getComments() != null ? oneOption.getComments() : "";
if ((UtilValidate.isEmpty(comments) && UtilValidate.isEmpty(productConfigConfig.getString("description"))) || comments.equals(productConfigConfig.getString("description"))) {
configsToCheck.add(productConfigConfig);
}
}
}
}
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
}
}
if (UtilValidate.isNotEmpty(configsToCheck)) {
for (GenericValue productConfigConfig: configsToCheck) {
String tempConfigId = productConfigConfig.getString("configId");
try {
List<GenericValue> tempResult = EntityQuery.use(delegator).from("ProductConfigConfig").where("configId",tempConfigId).queryList();
if (tempResult.size() == selectedOptionSize && configsToCheck.containsAll(tempResult)) {
List<GenericValue> configOptionProductOptions = EntityQuery.use(delegator).from("ConfigOptionProductOption").where("configId",tempConfigId).queryList();
if (UtilValidate.isNotEmpty(configOptionProductOptions)) {
// check for variant product equality
for (ConfigItem ci: questions) {
String configItemId = null;
Long sequenceNum = null;
List<ProductConfigWrapper.ConfigOption> selectedOptions = FastList.newInstance();
List<ConfigOption> options = ci.getOptions();
if (ci.isStandard()) {
selectedOptions.addAll(options);
} else {
for (ConfigOption oneOption: options) {
if (oneOption.isSelected()) {
selectedOptions.add(oneOption);
}
}
}
boolean match = true;
for (ProductConfigWrapper.ConfigOption anOption : selectedOptions) {
if (match && anOption.hasVirtualComponent()) {
List<GenericValue> components = anOption.getComponents();
for (GenericValue aComponent : components) {
if (anOption.isVirtualComponent(aComponent)) {
Map<String, String> componentOptions = anOption.getComponentOptions();
String optionProductId = aComponent.getString("productId");
String optionProductOptionId = componentOptions.get(optionProductId);
String configOptionId = anOption.configOption.getString("configOptionId");
configItemId = ci.getConfigItemAssoc().getString("configItemId");
sequenceNum = ci.getConfigItemAssoc().getLong("sequenceNum");
GenericValue configOptionProductOption = delegator.makeValue("ConfigOptionProductOption");
configOptionProductOption.set("configId", tempConfigId);
configOptionProductOption.set("configItemId",configItemId);
configOptionProductOption.set("sequenceNum", sequenceNum);
configOptionProductOption.set("configOptionId", configOptionId);
configOptionProductOption.set("productId", optionProductId);
configOptionProductOption.set("productOptionId", optionProductOptionId);
if (!configOptionProductOptions.remove(configOptionProductOption)) {
match = false;
break;
}
}
}
}
}
if (match && (UtilValidate.isEmpty(configOptionProductOptions))) {
configWrapper.configId = tempConfigId;
Debug.logInfo("Existing configuration found with configId:"+ tempConfigId, module);
return;
}
}
} else {
configWrapper.configId = tempConfigId;
Debug.logInfo("Existing configuration found with configId:"+ tempConfigId, module);
return;
}
}
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
}
}
//Current configuration is not found in ProductConfigConfig entity. So lets store this one
boolean nextId = true;
for (ConfigItem ci: questions) {
String configItemId = null;
Long sequenceNum = null;
List<ProductConfigWrapper.ConfigOption> selectedOptions = FastList.newInstance();
List<ConfigOption> options = ci.getOptions();
if (ci.isStandard()) {
selectedOptions.addAll(options);
} else {
for (ConfigOption oneOption: options) {
if (oneOption.isSelected()) {
selectedOptions.add(oneOption);
}
}
}
if (selectedOptions.size() > 0) {
if (nextId) {
configId = delegator.getNextSeqId("ProductConfigConfig");
//get next configId only once and only if there are selectedOptions
nextId = false;
}
configItemId = ci.getConfigItemAssoc().getString("configItemId");
sequenceNum = ci.getConfigItemAssoc().getLong("sequenceNum");
for (ConfigOption oneOption: selectedOptions) {
List<GenericValue> toBeStored = FastList.newInstance();
String configOptionId = oneOption.configOption.getString("configOptionId");
String description = oneOption.getComments();
GenericValue productConfigConfig = delegator.makeValue("ProductConfigConfig");
productConfigConfig.put("configId", configId);
productConfigConfig.put("configItemId", configItemId);
productConfigConfig.put("sequenceNum", sequenceNum);
productConfigConfig.put("configOptionId", configOptionId);
productConfigConfig.put("description", description);
toBeStored.add(productConfigConfig);
if (oneOption.hasVirtualComponent()) {
List<GenericValue> components = oneOption.getComponents();
for (GenericValue component: components) {
if (oneOption.isVirtualComponent(component)) {
String componentOption = oneOption.componentOptions.get(component.getString("productId"));
GenericValue configOptionProductOption = delegator.makeValue("ConfigOptionProductOption");
configOptionProductOption.put("configId", configId);
configOptionProductOption.put("configItemId", configItemId);
configOptionProductOption.put("sequenceNum", sequenceNum);
configOptionProductOption.put("configOptionId", configOptionId);
configOptionProductOption.put("productId", component.getString("productId"));
configOptionProductOption.put("productOptionId", componentOption);
toBeStored.add(configOptionProductOption);
}
}
}
try {
delegator.storeAll(toBeStored);
} catch (GenericEntityException e) {
configId = null;
Debug.logWarning(e.getMessage(), module);
}
}
}
}
//save configId to configWrapper, so we can use it in shopping cart operations
configWrapper.configId = configId;
Debug.logInfo("New configId created:"+ configId, module);
return;
}
/**
* Creates a new ProductConfigWrapper for productId and configures it according to ProductConfigConfig entity with configId
* ProductConfigConfig entity stores only the selected options, and the product price is calculated from input params
* @param delegator
* @param dispatcher
* @param configId configuration Id
* @param productId AGGRAGATED productId
* @param productStoreId needed for price calculations
* @param catalogId needed for price calculations
* @param webSiteId needed for price calculations
* @param currencyUomId needed for price calculations
* @param locale
* @param autoUserLogin
* @return ProductConfigWrapper
*/
public static ProductConfigWrapper loadProductConfigWrapper(Delegator delegator, LocalDispatcher dispatcher, String configId, String productId, String productStoreId, String catalogId, String webSiteId, String currencyUomId, Locale locale, GenericValue autoUserLogin) {
ProductConfigWrapper configWrapper = null;
try {
configWrapper = new ProductConfigWrapper(delegator, dispatcher, productId, productStoreId, catalogId, webSiteId, currencyUomId, locale, autoUserLogin);
if (configWrapper != null && UtilValidate.isNotEmpty(configId)) {
configWrapper.loadConfig(delegator, configId);
}
} catch (Exception e) {
Debug.logWarning(e.getMessage(), module);
configWrapper = null;
}
return configWrapper;
}
}