All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.cq.commerce.common.CommerceHelper Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2011 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.adobe.cq.commerce.common;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.RepositoryException;

import com.adobe.cq.commerce.api.CommerceConstants;
import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceSession;
import com.adobe.cq.commerce.api.OrderHistoryTraitDataProvider;
import com.adobe.cq.commerce.api.PlacedOrderResult;
import com.adobe.cq.commerce.api.PriceInfo;
import com.adobe.cq.commerce.api.Product;
import com.adobe.cq.commerce.api.promotion.Promotion;
import com.adobe.cq.commerce.api.promotion.PromotionManager;
import com.adobe.cq.commerce.api.promotion.VoucherInfo;
import com.day.cq.commons.LanguageUtil;
import com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap;
import com.day.cq.commons.inherit.InheritanceValueMap;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.wcm.api.PageManager;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.commerce.api.promotion.PromotionInfo;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagManager;
import com.day.cq.wcm.api.Page;

public class CommerceHelper {

    private static final Logger log = LoggerFactory.getLogger(CommerceHelper.class);

    /**
     * Maps a designer-supplied resource path (such as /content/geometrixx-outdoors/language-masters/en/user/cart)
     * to the current language (ie: /content/geometrixx-outdoors/de/de/user/cart).
     * @param currentPage The page being rendered (which controls the language selection)
     * @param srcPath The resource path (usually given in the design language)
     * @return The srcPath mapped to the currentPage's language
     */
    public static String mapPathToCurrentLanguage(Page currentPage, String srcPath) {
        // Example:
        //   currentPage: /content/geometrixx-outdoors/de/de/men/shorts/marka-sport
        //   srcPath:     /content/geometrixx-outdoors/language-masters/en/user/cart
        //   return:      /content/geometrixx-outdoors/de/de/user/cart

        final String languageRoot = LanguageUtil.getLanguageRoot(currentPage.getPath());
        if (StringUtils.isNotEmpty(languageRoot) && StringUtils.isNotBlank(srcPath)) {
            final String srcSuffix = StringUtils.substringAfter(srcPath, LanguageUtil.getLanguageRoot(srcPath));
            return(languageRoot+srcSuffix);
        } else {
            return srcPath;
        }
    }

    /**
     * Handles comparison of product "size" fields containing either numeric or letter sizes.
     * Both operands must have the same type of size (ie: numeric or letter).
     * @return a java.util.Comparator
     */
    public static Comparator getProductSizeComparator() {
        final String[] letterSizes = {"XXS", "2XS", "XS", "S", "M", "L", "XL", "XXL", "2XL", "XXXL", "3XL"};
        final String[] units = {"IN", "FT", "MM", "CM", "M"};

        return new Comparator() {
            public int compare(Product o1, Product o2) {
                String size1 = o1.getProperty("size", String.class).toUpperCase();
                String size2 = o2.getProperty("size", String.class).toUpperCase();
                if (indexOf(letterSizes, size1) >= 0 && indexOf(letterSizes, size2) >= 0) {
                    return NumberUtils.compare(indexOf(letterSizes, size1), indexOf(letterSizes, size2));
                }
                // remove units if both values share same unit
                for (String unit : units) {
                    if (size1.contains(unit) && size2.contains(unit)) {
                        size1 = size1.replace(unit, "");
                        size2 = size2.replace(unit, "");
                    }
                }
                // convert feet and inches to monotonic function
                if (size1.contains("\"")) {
                    size1 = size1.replace("\"", "");
                }
                if (size1.indexOf("'") >= 1) {
                    String parts[] = size1.split("'");
                    size1 = parts[0];
                    if (parts.length == 1) {
                        size1 += "00";
                    } else if (parts[1].length() == 1) {
                        size1 += "0" + parts[1];
                    } else {
                        size1 += parts[1];
                    }
                }
                if (size2.contains("\"")) {
                    size2 = size2.replace("\"", "");
                }
                if (size2.indexOf("'") >= 1) {
                    String parts[] = size2.split("'");
                    size2 = parts[0];
                    if (parts.length == 1) {
                        size2 += "00";
                    } else if (parts[1].length() == 1) {
                        size2 += "0" + parts[1];
                    } else {
                        size2 += parts[1];
                    }
                }
                double d1, d2;
                if (NumberUtils.isNumber(size1) && NumberUtils.isNumber(size2)) {
                    d1 = Double.parseDouble(size1);
                    d2 = Double.parseDouble(size2);
                } else {
                    throw new IllegalArgumentException(String.format("Size values not recognized: %s, %s",
                            o1.getProperty("size", String.class), o2.getProperty("size", String.class)));
                }
                return NumberUtils.compare(d1, d2);
            }
        };
    }

    private static int indexOf(String[] strings, String op) {
        for (int i = 0; i < strings.length; i++) {
            if (strings[i].equals(op)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Finds the current {@link Product} for a product page. This will be the first {@link Product}
     * node or reference within the page's content.
     * @param currentPage The page to search in
     * @return First Product for a product page
     * @see #findProduct(Resource)
     */
    public static Product findCurrentProduct(Page currentPage) {
        try {
            return findProduct(currentPage.getContentResource());
        } catch (RepositoryException e) {
            return null;
        }
    }

    /**
     * Finds a product at or below the provided resource's path.
     * @param resource The resource to start from
     * @return First Product that is found
     * @throws RepositoryException
     */
    protected static Product findProduct(Resource resource) throws RepositoryException {
        final Resource productResource = findProductResource(resource);
        if (productResource != null) {
            return productResource.adaptTo(Product.class);
        }
        return null;
    }

    /**
     * Finds the current product resource for a page. This will be the first node with a {@code cq:commerceType}
     * property of {@code product}. As opposed to {@link #findCurrentProduct(com.day.cq.wcm.api.Page)}, this will not
     * try to adapt the resource to a {@link Product}, so it will work even for yet-unfilled resources.
     * @param page The page to search in
     * @return The first {@code cq:commerceType='product'} resource, or {@code null} if it doesn't exist
     * @see #findProductResource(org.apache.sling.api.resource.Resource)
     */
    public static Resource findProductResource(final Page page) {
        return findProductResource(page.getContentResource());
    }

    /**
     * Finds a product resource at or below the provided resource's path. As opposed to
     * {@link #findProduct(org.apache.sling.api.resource.Resource)}, this will not try to adapt the resource to a
     * {@link Product}, so it will work even for yet-unfilled resources.
     * @param resource The resource to start from
     * @return The first {@code cq:commerceType='product'} resource, or {@code null} if it doesn't exist
     * @see #findProductResource(com.day.cq.wcm.api.Page)
     */
    public static Resource findProductResource(final Resource resource) {
        if (AbstractJcrProduct.isABaseProduct(resource)) {
            return resource;
        } else {
            for (Iterator iter = resource.listChildren(); iter.hasNext();) {
                final Resource product = findProductResource(iter.next());
                if (product != null) {
                    return product;
                }
            }
            return null;
        }
    }

    /**
     * Collects up a map of product variations (including the base product) under a given subtree.
     * The map is keyed by the PN_PRODUCT_DATA path.
     * @param resource
     * @param variations
     */
    public static void collectVariants(final Resource resource, Mapvariations) {
        if (AbstractJcrProduct.isAProductOrVariant(resource)) {
            ValueMap properties = resource.adaptTo(ValueMap.class);
            String key = properties.get(CommerceConstants.PN_PRODUCT_DATA, String.class);
            if (key != null && !properties.get("cq:RolloutHookSyncAction", false)) {
                variations.put(key, resource.adaptTo(Product.class));
            }
        }
        for (final Iterator children = resource.listChildren(); children.hasNext();) {
            collectVariants(children.next(), variations);
        }
    }

    /**
     * Copies a filtered list of tags from a Product to another Resource.
     * @param source
     * @param dest
     * @param filter    A {@link Predicate} which is passed each {@link com.day.cq.tagging.Tag}.
     * @return          true if any tags were copied
     */
    public static boolean copyTags(Product source, Resource dest, Predicate filter) throws RepositoryException {
        TagManager tagManager = dest.getResourceResolver().adaptTo(TagManager.class);
        List resultTags = new ArrayList();
        boolean changed = false;

        String[] destTags = ResourceUtil.getValueMap(dest).get("cq:tags", String[].class);
        if (destTags != null) {
            resultTags.addAll(Arrays.asList(destTags));
        }

        String[] sourceTags = source.getProperty("cq:tags", String[].class);
        if (sourceTags != null) {
            for (String sourceTag : sourceTags) {
                Tag tag = tagManager.resolve(sourceTag);
                if (tag != null && filter.evaluate(tag) && !resultTags.contains(sourceTag)) {
                    resultTags.add(sourceTag);
                    changed = true;
                }
            }
        }

        if (changed) {
            dest.adaptTo(Node.class).setProperty("cq:tags", resultTags.toArray(new String[resultTags.size()]));
        }
        return changed;
    }

    /**
     * Writes a cart to JSON for the CartMgr ClientContextStore.
     * Does not support server promotions or promotion maps.
     *
     * @param writer A JSONWriter instance
     * @param commerceSession The commerce session
     * @throws CommerceException
     * @throws JSONException
     */
    public static void writeCart(JSONWriter writer, CommerceSession commerceSession)
            throws CommerceException, JSONException {
        writeCart(writer, commerceSession, null);
    }

    /**
     * Writes a cart to JSON for the CartMgr ClientContextStore.
     * Supports server promotions, but not promotion maps.
     *
     * @param writer A JSONWriter instance
     * @param commerceSession The commerce session
     * @param resolver The resource resolver
     * @throws CommerceException
     * @throws JSONException
     * @deprecated since 6.1
     */
    @Deprecated
    public static void writeCart(JSONWriter writer, CommerceSession commerceSession, ResourceResolver resolver)
            throws CommerceException, JSONException {
        writeCart(writer, commerceSession, resolver, null);
    }

    /**
     * Writes a cart to JSON for the CartMgr ClientContextStore.
     * Supports server promotions and promotionMaps.
     *
     * @param writer A JSONWriter instance
     * @param commerceSession The commerce session
     * @param resolver The resource resolver
     * @param request The current request
     * @throws CommerceException
     * @throws JSONException
     * @deprecated since 6.1 (moved to ClientContextCartServlet)
     */
    @Deprecated
    public static void writeCart(JSONWriter writer, CommerceSession commerceSession, ResourceResolver resolver,
                                 SlingHttpServletRequest request) throws CommerceException, JSONException {

        PromotionManager pm = resolver != null ? resolver.adaptTo(PromotionManager.class) : null;

        //
        // Fetch the server and client promotion:segments maps
        //
        Map serverPromotionsMap = new HashMap();
        if (pm != null) {
            for (Promotion promotion : pm.getAvailablePromotions(resolver)) {
                if ("/libs/commerce/components/promotion/server".equals(promotion.getType())) {
                    ValueMap props = promotion.getConfig();
                    String code = props.get("code", "");
                    if (StringUtils.isNotBlank(code) && !props.get("disabled", Boolean.FALSE)) {
                        serverPromotionsMap.put(code, StringUtils.join(promotion.getSegments(), ","));
                    }
                }
            }
        }
        Map clientPromotionsMap = new HashMap();
        if (pm != null && request != null) {
            clientPromotionsMap = pm.getPromotionsMap(request);
        }

        writer.object();
        {
            writer.key("entries").array();
            for (CommerceSession.CartEntry e : commerceSession.getCartEntries()) {
                writer.object();
                {
                    writer.key("title").value(e.getProduct().getTitle());
                    writer.key("quantity").value(e.getQuantity());
                    final List priceInfos = e.getPriceInfo(null);
                    if (priceInfos != null && priceInfos.size() > 0) {
                        writer.key("priceFormatted").value(priceInfos.get(0).getFormattedString());
                        writer.key("price").value(priceInfos.get(0).getAmount());
                    } else {
                        log.error("Empty PriceInfo list for product!", e.getProduct().getPath());
                        writer.key("priceFormatted").value("$0.00");
                        writer.key("price").value(0);
                    }
                    String thumbnail = e.getProduct().getThumbnailUrl(".64.transparent");
                    if (StringUtils.isNotEmpty(thumbnail)) {
                        writer.key("thumbnail").value(thumbnail);
                    }
                    writer.key("page").value(e.getProduct().getPagePath());
                    writer.key("path").value(e.getProduct().getPath());
                    Boolean readonly= e.getProperty(CommerceSession.PN_READONLY, Boolean.class);
                    if (readonly != null && readonly) {
                        writer.key("readonly").value(true);
                    }
                    if (e instanceof DefaultJcrCartEntry) {
                        DefaultJcrCartEntry entry = (DefaultJcrCartEntry) e;
                        ValueMap properties = entry.getProperties();
                        if (!properties.isEmpty()) {
                            writer.key("properties").object();
                            for (String key : properties.keySet()) {
                                writer.key(key).value(properties.get(key, String.class));
                            }
                            writer.endObject();
                        }
                    }
                }
                writer.endObject();
            }
            writer.endArray();

            writer.key("promotions").array();
            for (PromotionInfo p : commerceSession.getPromotions()) {
                if (p.getStatus() == PromotionInfo.PromotionStatus.FIRED || p.getStatus() == PromotionInfo.PromotionStatus.POTENTIAL) {
                    writer.object();
                    {
                        writer.key("title").value(p.getTitle());
                        writer.key("description").value(p.getDescription());
                        writer.key("message").value(p.getMessage());
                        writer.key("path").value(p.getPath());
                        writer.key("status").value(p.getStatus().toString());
                        writer.key("cartEntryIndex").value(p.getCartEntryIndex());
                        if (p.getStatus().equals(PromotionInfo.PromotionStatus.FIRED) && serverPromotionsMap.containsKey(p.getPath())) {
                            writer.key("resolve").value("true");
                        }
                    }
                    writer.endObject();
                }
            }
            writer.endArray();

            writer.key("vouchers").array();
            for (VoucherInfo c : commerceSession.getVoucherInfos()) {
                writer.object();
                {
                    writer.key("code").value(c.getCode());
                    writer.key("title").value(c.getTitle());
                    writer.key("description").value(c.getDescription());
                    writer.key("message").value(c.getMessage());
                    writer.key("path").value(c.getPath());
                }
                writer.endObject();
            }
            writer.endArray();

            final List priceInfos = commerceSession.getCartPriceInfo(null);
            if (priceInfos != null && priceInfos.size() > 0) {
                writer.key("totalPrice").value(priceInfos.get(0).getFormattedString());
                writer.key("totalPriceFloat").value(priceInfos.get(0).getAmount());
            } else {
                log.error("Empty PriceInfo list for cart!");
                writer.key("totalPrice").value("$0.00");
                writer.key("totalPriceFloat").value(0);
            }

            writer.key("promotionsMap").array();
            Map map = commerceSession.supportsClientsidePromotionResolution() ? clientPromotionsMap : serverPromotionsMap;
            for (Map.Entry entry : map.entrySet()) {
                writer.object();
                {
                    writer.key("segments").value(entry.getValue());
                    writer.key("path").value(entry.getKey());
                }
                writer.endObject();
            }
            writer.endArray();
        }
        writer.endObject();
    }

    /**
     * Writes order history trait data to JSON for the OrderHistoryMgr ClientContextStore.
     *
     * @param writer A JSONWriter instance
     * @param orderHistory the placed orders
     * @param services the order history trait data provider services
     * @throws CommerceException
     * @throws JSONException
     */
    public static void writeOrderHistory(JSONWriter writer, PlacedOrderResult orderHistory,
             OrderHistoryTraitDataProvider[] services) throws CommerceException, JSONException {

        if (services == null) {
            services = new OrderHistoryTraitDataProvider[0];
        }

        writer.object();

        writer.key("traits").object();

        Arrays.sort(services, new Comparator() {
            @Override
            public int compare(OrderHistoryTraitDataProvider o1, OrderHistoryTraitDataProvider o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        for (OrderHistoryTraitDataProvider trait : services) {
            writer.key(trait.getIdentifier()).object();
            writer.key("name").value(trait.getName());
            writer.key("data");

            Object data = trait.getTraitData(orderHistory);

            if (data instanceof Map) {
                writer.object();

                @SuppressWarnings("unchecked")
                Map map = (Map) data;
                for (Map.Entry entry : map.entrySet()) {
                    writer.key(entry.getKey());
                    writer.value(entry.getValue());
                }

                writer.endObject();
            } else if (data instanceof Collection){
                writer.array();
                for (Object o : (Collection) data) {
                    writer.value(o);
                }
                writer.endArray();
            } else {
                writer.value(data);
            }

            writer.endObject();
        }

        writer.endObject();
        writer.endObject();
    }

    /**
     * Returns a thumbnail for a card in the catalogs console.
     * @param contextPath the request's contextPath
     */
    public static String getCatalogCardThumbnail(String contextPath, Resource resource, PageManager pageManager) {
        ValueMap properties = resource.adaptTo(ValueMap.class);
        Page templatePage = null;
        if (ResourceUtil.isA(resource, "commerce/components/catalog")) {
            InheritanceValueMap blueprintProps = new HierarchyNodeInheritanceValueMap(resource);
            templatePage = pageManager.getPage(blueprintProps.getInherited("templates/catalog", ""));
        } else if (ResourceUtil.isA(resource, "commerce/components/section")) {
            InheritanceValueMap blueprintProps = new HierarchyNodeInheritanceValueMap(resource);
            templatePage = pageManager.getPage(blueprintProps.getInherited("templates/section", ""));
        }

        if (templatePage != null) {
            String url = contextPath + templatePage.getPath() + ".thumb.319.319.png";
            Calendar lastMod = templatePage.getProperties().get("image/file/jcr:content/jcr:lastModified", Calendar.class);
            if (lastMod != null) {
                url += "?ck=" + (lastMod.getTimeInMillis() / 1000);
            }
            return url;
        } else {
            String coverUrl = properties.get("coverUrl", String.class);
            if (coverUrl != null) {
                return coverUrl + ".thumb.319.319.png";
            } else {
                return contextPath + resource.getParent().getPath() + ".thumb.319.319.png";
            }
        }
    }

    /**
     * Return a title suitable for a card in the touch-optimized GUI.
     * @param resource
     * @param pageManager
     * @return
     */
    public static String getCardTitle(Resource resource, PageManager pageManager) {
        String title;
        if (AbstractJcrProduct.isAProductOrVariant(resource)) {
            String jcrTitle = resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class);
            if (StringUtils.isNotBlank(jcrTitle)) {
                return jcrTitle;
            }

            // No distinct title found.  Walk up the product hierarchy, prepending intermediate
            // nodeNames until we get a distinct title.
            title = resource.getName();
            resource = resource.getParent();
            while (resource != null) {
                jcrTitle = resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class);
                if (StringUtils.isNotBlank(jcrTitle)) {
                    // found a distinct title; prepend it and we're done:
                    title = jcrTitle + " / " + title;
                    return title;
                }
                title = resource.getName() + " / " + title;
                resource = resource.getParent();
            }

            return title;
        }

        Page page = pageManager.getPage(resource.getPath());
        if (page != null) {
            return page.getTitle();
        }

        Resource jcr_content = resource.getChild("jcr:content");
        if (jcr_content != null) {
            String jcrTitle = jcr_content.getValueMap().get(JcrConstants.JCR_TITLE, String.class);
            if (StringUtils.isNotBlank(jcrTitle)) {
                return jcrTitle;
            }
        }
        String jcrTitle = resource.getValueMap().get(JcrConstants.JCR_TITLE, String.class);
        if (StringUtils.isNotBlank(jcrTitle)) {
            return jcrTitle;
        }

        // No author-supplied title; go with the node name.
        title = resource.getName();

        // Check to see if we're in what looks like a date hierarchy (or other type
        // of numeric bucketing), and if so, add a bit more context.
        if (title.matches("\\d+")) {
            resource = resource.getParent();
            while (resource.getName().matches("\\d+")) {
                title = resource.getName() + "." + title;
                resource = resource.getParent();
            }
        }
        return title;
    }

    /**
     * Return an externalized URL to a product's thumbnail image.
     * @param contextPath the request's contextPath
     */
    public static String getProductCardThumbnail(String contextPath, Product product) {
        String thumbnail = product.getThumbnailUrl(319);
        if (StringUtils.isNotEmpty(thumbnail)) {
            return contextPath + thumbnail;
        }
        return null;
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy