/******************************************************************************* * 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.apache.ofbiz.product.category; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilDateTime; import org.apache.ofbiz.base.util.UtilFormatOut; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.condition.EntityOperator; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityUtil; import org.apache.ofbiz.product.product.ProductWorker; import org.apache.ofbiz.service.DispatchContext; import org.apache.ofbiz.service.ServiceUtil; /** * CategoryWorker - Worker class to reduce code in JSPs. */ public final class CategoryWorker { public static final String module = CategoryWorker.class.getName(); private CategoryWorker () {} public static String getCatalogTopCategory(ServletRequest request, String defaultTopCategory) { HttpServletRequest httpRequest = (HttpServletRequest) request; Map<String, Object> requestParameters = UtilHttp.getParameterMap(httpRequest); String topCatName = null; boolean fromSession = false; // first see if a new category was specified as a parameter topCatName = (String) requestParameters.get("CATALOG_TOP_CATEGORY"); // if no parameter, try from session if (topCatName == null) { topCatName = (String) httpRequest.getSession().getAttribute("CATALOG_TOP_CATEGORY"); if (topCatName != null) fromSession = true; } // if nothing else, just use a default top category name if (topCatName == null) topCatName = defaultTopCategory; if (topCatName == null) topCatName = "CATALOG1"; if (!fromSession) { if (Debug.infoOn()) Debug.logInfo("[CategoryWorker.getCatalogTopCategory] Setting new top category: " + topCatName, module); httpRequest.getSession().setAttribute("CATALOG_TOP_CATEGORY", topCatName); } return topCatName; } public static void getCategoriesWithNoParent(ServletRequest request, String attributeName) { Delegator delegator = (Delegator) request.getAttribute("delegator"); Collection<GenericValue> results = new LinkedList<GenericValue>(); try { Collection<GenericValue> allCategories = EntityQuery.use(delegator).from("ProductCategory").queryList(); for (GenericValue curCat: allCategories) { Collection<GenericValue> parentCats = curCat.getRelated("CurrentProductCategoryRollup", null, null, true); if (parentCats.isEmpty()) results.add(curCat); } } catch (GenericEntityException e) { Debug.logWarning(e, module); } request.setAttribute(attributeName, results); } public static void getRelatedCategories(ServletRequest request, String attributeName, boolean limitView) { Map<String, Object> requestParameters = UtilHttp.getParameterMap((HttpServletRequest) request); String requestId = null; requestId = UtilFormatOut.checkNull((String)requestParameters.get("catalog_id"), (String)requestParameters.get("CATALOG_ID"), (String)requestParameters.get("category_id"), (String)requestParameters.get("CATEGORY_ID")); if (requestId.equals("")) return; if (Debug.infoOn()) Debug.logInfo("[CategoryWorker.getRelatedCategories] RequestID: " + requestId, module); getRelatedCategories(request, attributeName, requestId, limitView); } public static void getRelatedCategories(ServletRequest request, String attributeName, String parentId, boolean limitView) { getRelatedCategories(request, attributeName, parentId, limitView, false); } public static void getRelatedCategories(ServletRequest request, String attributeName, String parentId, boolean limitView, boolean excludeEmpty) { List<GenericValue> categories = getRelatedCategoriesRet(request, attributeName, parentId, limitView, excludeEmpty); if (!categories.isEmpty()) request.setAttribute(attributeName, categories); } public static List<GenericValue> getRelatedCategoriesRet(ServletRequest request, String attributeName, String parentId, boolean limitView) { return getRelatedCategoriesRet(request, attributeName, parentId, limitView, false); } public static List<GenericValue> getRelatedCategoriesRet(ServletRequest request, String attributeName, String parentId, boolean limitView, boolean excludeEmpty) { return getRelatedCategoriesRet(request, attributeName, parentId, limitView, excludeEmpty, false); } public static List<GenericValue> getRelatedCategoriesRet(ServletRequest request, String attributeName, String parentId, boolean limitView, boolean excludeEmpty, boolean recursive) { Delegator delegator = (Delegator) request.getAttribute("delegator"); return getRelatedCategoriesRet(delegator, attributeName, parentId, limitView, excludeEmpty, recursive); } public static List<GenericValue> getRelatedCategoriesRet(Delegator delegator, String attributeName, String parentId, boolean limitView, boolean excludeEmpty, boolean recursive) { List<GenericValue> categories = new LinkedList<GenericValue>(); if (Debug.verboseOn()) Debug.logVerbose("[CategoryWorker.getRelatedCategories] ParentID: " + parentId, module); List<GenericValue> rollups = null; try { rollups = EntityQuery.use(delegator).from("ProductCategoryRollup").where("parentProductCategoryId", parentId).orderBy("sequenceNum").cache(true).queryList(); if (limitView) { rollups = EntityUtil.filterByDate(rollups, true); } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } if (rollups != null) { for (GenericValue parent: rollups) { GenericValue cv = null; try { cv = parent.getRelatedOne("CurrentProductCategory", true); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } if (cv != null) { if (excludeEmpty) { if (!isCategoryEmpty(cv)) { categories.add(cv); if (recursive) { categories.addAll(getRelatedCategoriesRet(delegator, attributeName, cv.getString("productCategoryId"), limitView, excludeEmpty, recursive)); } } } else { categories.add(cv); if (recursive) { categories.addAll(getRelatedCategoriesRet(delegator, attributeName, cv.getString("productCategoryId"), limitView, excludeEmpty, recursive)); } } } } } return categories; } public static boolean isCategoryEmpty(GenericValue category) { boolean empty = true; long members = categoryMemberCount(category); if (members > 0) { empty = false; } if (empty) { long rollups = categoryRollupCount(category); if (rollups > 0) { empty = false; } } return empty; } public static long categoryMemberCount(GenericValue category) { if (category == null) return 0; Delegator delegator = category.getDelegator(); long count = 0; try { count = EntityQuery.use(delegator).from("ProductCategoryMember").where("productCategoryId", category.getString("productCategoryId")).queryCount(); } catch (GenericEntityException e) { Debug.logError(e, module); } return count; } public static long categoryRollupCount(GenericValue category) { if (category == null) return 0; Delegator delegator = category.getDelegator(); long count = 0; try { count = EntityQuery.use(delegator).from("ProductCategoryRollup").where("parentProductCategoryId", category.getString("productCategoryId")).queryCount(); } catch (GenericEntityException e) { Debug.logError(e, module); } return count; } private static EntityCondition buildCountCondition(String fieldName, String fieldValue) { List<EntityCondition> orCondList = new LinkedList<EntityCondition>(); orCondList.add(EntityCondition.makeCondition("thruDate", EntityOperator.GREATER_THAN, UtilDateTime.nowTimestamp())); orCondList.add(EntityCondition.makeCondition("thruDate", EntityOperator.EQUALS, null)); EntityCondition orCond = EntityCondition.makeCondition(orCondList, EntityOperator.OR); List<EntityCondition> andCondList = new LinkedList<EntityCondition>(); andCondList.add(EntityCondition.makeCondition("fromDate", EntityOperator.LESS_THAN, UtilDateTime.nowTimestamp())); andCondList.add(EntityCondition.makeCondition(fieldName, EntityOperator.EQUALS, fieldValue)); andCondList.add(orCond); EntityCondition andCond = EntityCondition.makeCondition(andCondList, EntityOperator.AND); return andCond; } public static void setTrail(ServletRequest request, String currentCategory) { Map<String, Object> requestParameters = UtilHttp.getParameterMap((HttpServletRequest) request); String previousCategory = (String) requestParameters.get("pcategory"); setTrail(request, currentCategory, previousCategory); } public static void setTrail(ServletRequest request, String currentCategory, String previousCategory) { if (Debug.verboseOn()) Debug.logVerbose("[CategoryWorker.setTrail] Start: previousCategory=" + previousCategory + " currentCategory=" + currentCategory, module); // if there is no current category, just return and do nothing to that the last settings will stay if (UtilValidate.isEmpty(currentCategory)) { return; } // always get the last crumb list List<String> crumb = getTrail(request); crumb = adjustTrail(crumb, currentCategory, previousCategory); setTrail(request, crumb); } public static List<String> adjustTrail(List<String> origTrail, String currentCategoryId, String previousCategoryId) { List<String> trail = new LinkedList<String>(); if (origTrail != null) { trail.addAll(origTrail); } // if no previous category was specified, check to see if currentCategory is in the list if (UtilValidate.isEmpty(previousCategoryId)) { if (trail.contains(currentCategoryId)) { // if cur category is in crumb, remove everything after it and return int cindex = trail.lastIndexOf(currentCategoryId); if (cindex < (trail.size() - 1)) { for (int i = trail.size() - 1; i > cindex; i--) { trail.remove(i); } } return trail; } else { // current category is not in the list, and no previous category was specified, go back to the beginning trail.clear(); trail.add("TOP"); if (UtilValidate.isNotEmpty(previousCategoryId)) { trail.add(previousCategoryId); } } } if (!trail.contains(previousCategoryId)) { // previous category was NOT in the list, ERROR, start over trail.clear(); trail.add("TOP"); if (UtilValidate.isNotEmpty(previousCategoryId)) { trail.add(previousCategoryId); } } else { // remove all categories after the previous category, preparing for adding the current category int index = trail.indexOf(previousCategoryId); if (index < (trail.size() - 1)) { for (int i = trail.size() - 1; i > index; i--) { trail.remove(i); } } } // add the current category to the end of the list trail.add(currentCategoryId); if (Debug.verboseOn()) Debug.logVerbose("[CategoryWorker.setTrail] Continuing list: Added currentCategory: " + currentCategoryId, module); return trail; } public static List<String> getTrail(ServletRequest request) { HttpSession session = ((HttpServletRequest) request).getSession(); List<String> crumb = UtilGenerics.checkList(session.getAttribute("_BREAD_CRUMB_TRAIL_")); return crumb; } public static List<String> setTrail(ServletRequest request, List<String> crumb) { HttpSession session = ((HttpServletRequest) request).getSession(); session.setAttribute("_BREAD_CRUMB_TRAIL_", crumb); return crumb; } public static boolean checkTrailItem(ServletRequest request, String category) { List<String> crumb = getTrail(request); if (crumb != null && crumb.contains(category)) { return true; } else { return false; } } public static String lastTrailItem(ServletRequest request) { List<String> crumb = getTrail(request); if (UtilValidate.isNotEmpty(crumb)) { return crumb.get(crumb.size() - 1); } else { return null; } } public static boolean isProductInCategory(Delegator delegator, String productId, String productCategoryId) throws GenericEntityException { if (productCategoryId == null) return false; if (UtilValidate.isEmpty(productId)) return false; List<GenericValue> productCategoryMembers = EntityQuery.use(delegator).from("ProductCategoryMember") .where("productCategoryId", productCategoryId, "productId", productId) .cache(true) .filterByDate() .queryList(); if (UtilValidate.isEmpty(productCategoryMembers)) { //before giving up see if this is a variant product, and if so look up the virtual product and check it... GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); List<GenericValue> productAssocs = ProductWorker.getVariantVirtualAssocs(product); //this does take into account that a product could be a variant of multiple products, but this shouldn't ever really happen... if (productAssocs != null) { for (GenericValue productAssoc: productAssocs) { if (isProductInCategory(delegator, productAssoc.getString("productId"), productCategoryId)) { return true; } } } return false; } else { return true; } } public static List<GenericValue> filterProductsInCategory(Delegator delegator, List<GenericValue> valueObjects, String productCategoryId) throws GenericEntityException { return filterProductsInCategory(delegator, valueObjects, productCategoryId, "productId"); } public static List<GenericValue> filterProductsInCategory(Delegator delegator, List<GenericValue> valueObjects, String productCategoryId, String productIdFieldName) throws GenericEntityException { List<GenericValue> newList = new LinkedList<GenericValue>(); if (productCategoryId == null) return newList; if (valueObjects == null) return null; for (GenericValue curValue: valueObjects) { String productId = curValue.getString(productIdFieldName); if (isProductInCategory(delegator, productId, productCategoryId)) { newList.add(curValue); } } return newList; } public static void getCategoryContentWrappers(Map<String, CategoryContentWrapper> catContentWrappers, List<GenericValue> categoryList, HttpServletRequest request) throws GenericEntityException { if (catContentWrappers == null || categoryList == null) { return; } for (GenericValue cat: categoryList) { String productCategoryId = (String) cat.get("productCategoryId"); if (catContentWrappers.containsKey(productCategoryId)) { // if this ID is already in the Map, skip it (avoids inefficiency, infinite recursion, etc.) continue; } CategoryContentWrapper catContentWrapper = new CategoryContentWrapper(cat, request); catContentWrappers.put(productCategoryId, catContentWrapper); List<GenericValue> subCat = getRelatedCategoriesRet(request, "subCatList", productCategoryId, true); if (subCat != null) { getCategoryContentWrappers(catContentWrappers, subCat, request); } } } /** * Returns a complete category trail - can be used for exporting proper category trees. * This is mostly useful when used in combination with bread-crumbs, for building a * faceted index tree, or to export a category tree for migration to another system. * Will create the tree from root point to categoryId. * * This method is not meant to be run on every request. * Its best use is to generate the trail every so often and store somewhere * (a lucene/solr tree, entities, cache or so). * * @param dctx The DispatchContext that this service is operating in * @param context Map containing the input parameters * @return Map organized trail from root point to categoryId. * */ public static Map getCategoryTrail(DispatchContext dctx, Map context) { String productCategoryId = (String) context.get("productCategoryId"); Map<String, Object> results = ServiceUtil.returnSuccess(); Delegator delegator = dctx.getDelegator(); List<String> trailElements = new LinkedList<String>(); trailElements.add(productCategoryId); String parentProductCategoryId = productCategoryId; while (UtilValidate.isNotEmpty(parentProductCategoryId)) { // find product category rollup try { List<EntityCondition> rolllupConds = new LinkedList<EntityCondition>(); rolllupConds.add(EntityCondition.makeCondition("productCategoryId", parentProductCategoryId)); rolllupConds.add(EntityUtil.getFilterByDateExpr()); List<GenericValue> productCategoryRollups = EntityQuery.use(delegator).from("ProductCategoryRollup").where(rolllupConds).orderBy("sequenceNum").cache(true).queryList(); if (UtilValidate.isNotEmpty(productCategoryRollups)) { // add only categories that belong to the top category to trail for (GenericValue productCategoryRollup : productCategoryRollups) { String trailCategoryId = productCategoryRollup.getString("parentProductCategoryId"); parentProductCategoryId = trailCategoryId; if (trailElements.contains(trailCategoryId)) { break; } else { trailElements.add(trailCategoryId); } } } else { parentProductCategoryId = null; } } catch (GenericEntityException e) { Map<String, String> messageMap = UtilMisc.toMap("errMessage", ". Cannot generate trail from product category. "); String errMsg = UtilProperties.getMessage("CommonUiLabels", "CommonDatabaseProblem", messageMap, (Locale) context.get("locale")); Debug.logError(e, errMsg, module); return ServiceUtil.returnError(errMsg); } } Collections.reverse(trailElements); results.put("trail", trailElements); return results; } }