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

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

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2012 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.Collections;
import java.util.Iterator;
import java.util.List;

import com.adobe.cq.commerce.api.CommerceConstants;
import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceService;
import com.adobe.cq.commerce.api.Product;
import com.adobe.cq.commerce.api.asset.ProductAssetManager;
import com.adobe.cq.commerce.api.VariantFilter;
import com.day.cq.commons.ImageResource;
import com.day.cq.commons.inherit.ComponentInheritanceValueMap;
import aQute.bnd.annotation.ConsumerType;
import com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap;
import com.day.cq.commons.inherit.InheritanceValueMap;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.DamConstants;
import com.day.cq.dam.commons.util.DamUtil;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.adapter.SlingAdaptable;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is meant to be used as a base class for a {@link Product} implementation built on top of
 * a JCR repository. GeoProductImpl is an example.
 */
@ConsumerType
public abstract class AbstractJcrProduct extends SlingAdaptable implements Product {

    private static final String[] IMG_MIME_TYPES = {
            DamConstants.THUMBNAIL_MIMETYPE, "image/jpeg", "image/tiff", "image/png",
            "image/bmp", "image/gif", "image/pipeg", "image/x-portable-anymap",
            "image/x-portable-bitmap", "image/x-portable-graymap",
            "image/x-portable-pixmap", "image/x-rgb", "image/x-xbitmap",
            "image/x-xpixmap", "image/x-icon", "image/photoshop",
            "image/x-photoshop", "image/psd", "application/photoshop", "application/psd",
            "image/vnd.adobe.photoshop"};

    private static final List IMG_MIME_TYPE = Arrays.asList(IMG_MIME_TYPES);

    // This first set is just here for backwards compatibility.  "Truth" is now held in CommerceConstants.
    protected static final String PN_PRODUCT_TYPE = CommerceConstants.PN_COMMERCE_TYPE;
    protected static final String PN_PRODUCT_VARIANT_AXES = CommerceConstants.PN_PRODUCT_VARIANT_AXES;
    protected static final String PN_PRODUCT_DATA = CommerceConstants.PN_PRODUCT_DATA;

    protected static final String PN_PRODUCT_TITLE = "jcr:title";
    protected static final String PN_PRODUCT_DESCRIPTION = "jcr:description";
    protected static final String PN_PRODUCT_ASSET_CATEGORY = "assetCategory";
    private static final String PN_ASSET_FILE_REFERENCE = "fileReference";

    protected static final Logger log = LoggerFactory.getLogger(AbstractJcrProduct.class);

    protected Resource resource;
    protected Product baseProduct;
    protected ProductAssetManager productAssetManager;

    /**
     * Constructor
     * @param resource the Resource storing the product (or variant) info
     */
    public AbstractJcrProduct(Resource resource) {
        this.resource = resource;
        this.productAssetManager = resource.getResourceResolver().adaptTo(ProductAssetManager.class);
    }

    @Override
    public String getPath() {
        return resource.getPath();
    }

    @Override
    public String getPagePath() {
        PageManager pm = resource.getResourceResolver().adaptTo(PageManager.class);
        Page page = pm.getContainingPage(resource);
        return (page != null) ? page.getPath() + ".html#" + getSKU() : null;
    }

    @Override
    public String getTitle() {
        return getProperty(PN_PRODUCT_TITLE, String.class);
    }

    @Override
    public String getTitle(String selectorString) {
        return getProperty(PN_PRODUCT_TITLE, selectorString, String.class);
    }

    @Override
    public String getDescription() {
        return getProperty(PN_PRODUCT_DESCRIPTION, String.class);
    }

    @Override
    public String getDescription(String selectorString) {
        return getProperty(PN_PRODUCT_DESCRIPTION, selectorString, String.class);
    }

    @Override
    @Deprecated
    public String getImagePath() {
        final ImageResource image = getImage();
        if (image != null) {
            return image.getPath();
        }
        return "";
    }

    @Override
    @Deprecated
    public String getImageUrl() {
        return getImagePath();
    }

    @Override
    @Deprecated
    public ImageResource getThumbnail() {
        final ImageResource thumbnail = getImage();
        if (thumbnail != null) {
            thumbnail.setSelector(".thumbnail");
        }
        return thumbnail;
    }

    @Override
    public String getThumbnailUrl() {
        return getThumbnailUrl("");
    }

    @Override
    public String getThumbnailUrl(int width) {
        final String selector = Integer.toString(width);
        return getThumbnailUrl(selector);
    }

    /**
     * When the {@code selectorString} contains "image", it returns the thumbnail based on the product image by
     * calling {@link #getImage()}, otherwise it returns the thumbnail based on the product asset by calling
     * {@link #getAsset()}
     */
    @Override
    public String getThumbnailUrl(String selectorString) {
        String selectors[] = (selectorString != null)? selectorString.split("\\.") : new String[] {""};
        Resource assetRes = Arrays.asList(selectors).contains("image") ? getImage() : getAsset();
        return assetRes == null ? null : productAssetManager.getThumbnailUrl(
                assetRes.getPath(), selectorString);
    }

    @Override
    public ImageResource getImage() {
        Resource image = getAsset(IMG_MIME_TYPE);
        if (image != null) {
            return new ImageResource(image);
        }
        return null;
    }

    @Override
    public Resource getAsset() {
        return getAsset(null);
    }

    protected Resource getAsset(List mimeTypes) {
        final List assets = getAssets(mimeTypes);

        // the product has one asset: we return it
        if (assets.size() == 1) {
            return assets.get(0);
        }

        // the product has several images
        if (assets.size() > 1) {

            // get the asset category to be displayed

            // 1. look up the product properties
            String assetCategoryToDisplay = resource.getValueMap().get(PN_PRODUCT_ASSET_CATEGORY, String.class);

            // 2. for a variant: look up the base product properties
            if (StringUtils.isEmpty(assetCategoryToDisplay) && isAVariant(resource)) {
                try {
                    baseProduct = this.getBaseProduct();
                    if (baseProduct != null) {
                        assetCategoryToDisplay = baseProduct.getProperty(PN_PRODUCT_ASSET_CATEGORY, String.class);
                    }
                } catch (CommerceException e) {
                    log.error("Error while retrieving the base product.", e);
                }
            }

            // 3. look up the asset category set by the blueprint for all the pages going up the hierarchy
            if (StringUtils.isEmpty(assetCategoryToDisplay)) {
                PageManager pm = resource.getResourceResolver().adaptTo(PageManager.class);
                Page page = pm.getContainingPage(resource);
                if (page != null) {
                    Resource pageContent = page.getContentResource();
                    InheritanceValueMap dataMap = new HierarchyNodeInheritanceValueMap(pageContent);
                    assetCategoryToDisplay = dataMap.getInherited(PN_PRODUCT_ASSET_CATEGORY, String.class);
                }
            }

            // 4. look up in the product data
            if (StringUtils.isEmpty(assetCategoryToDisplay)) {
                assetCategoryToDisplay = getProperty(PN_PRODUCT_ASSET_CATEGORY, String.class);
            }

            // find the asset that has the same assetCategory
            for (Resource asset : assets) {
                String category = asset.getValueMap().get(PN_PRODUCT_ASSET_CATEGORY, String.class);
                if (StringUtils.isNotEmpty(category) && category.equals(assetCategoryToDisplay)) {
                    return asset;
                }
            }

            //return the asset that contains the fileReference property
            for (Resource asset : assets) {
                if (StringUtils.isNotEmpty(asset.getValueMap().get(PN_ASSET_FILE_REFERENCE, String.class))) {
                    return asset;
                }
            }

            // 4. best effort: return the first asset of the set
            return assets.get(0);
        }
        return null;
    }

    @Override
    public List getImages() {
        final List result = new ArrayList();
        final List images = getAssets(IMG_MIME_TYPE);
        if (images == null || images.isEmpty()) {
            return result;
        }
        final Iterator imagesIterator = images.iterator();
        while (imagesIterator.hasNext()) {
            Resource assetRes = imagesIterator.next();
            result.add(new ImageResource(assetRes));
        }
        return result;
    }

    @Override
    public List getAssets() {
        // See if there's an immediate value:
        List assets = getAssets(resource);
        if (assets.size() > 0) {
            return assets;
        }

        // Else check pim reference or inheritance:
        Resource ancestor;
        final String productDataPath = resource.getValueMap().get(PN_PRODUCT_DATA, String.class);
        if (StringUtils.isNotEmpty(productDataPath)) {
            ancestor = resource.getResourceResolver().getResource(productDataPath);
            if (ancestor == null) {
                log.warn("Product data not found at [{}].", productDataPath);
                return Collections.emptyList();
            }
        } else {
            ancestor = resource.getParent();
        }
        while (ancestor != null && isAProductOrVariant(ancestor)) {
            assets = getAssets(ancestor);
            if (assets.size() > 0) {
                return assets;
            }
            ancestor = ancestor.getParent();
        }

        return Collections.emptyList();
    }

    /**
     * Returns the assets filtered by the mime type
     * @param mimeTypes
     * @return
     */
    public List getAssets(List mimeTypes) {
        final List assets = getAssets();
        return filterResources(assets, mimeTypes);
    }

    // get all the assets below the product node
    protected List getAssets(final Resource resource) {
        final List result = new ArrayList();
        // add the legacy image/ node to the assets
        final Resource legacyImageResource = resource.getChild("image");
        if (legacyImageResource != null) {
            result.add(legacyImageResource);
        }
        // add the assets below assets/ to the assets
        final Resource assetsResource = resource.getChild("assets");
        if (assetsResource != null) {
            final Iterator assets = assetsResource.listChildren();
            while (assets.hasNext()) {
                result.add(assets.next());
            }
        }
        return result;
    }

    // Returns the assets filtered by the mime type
    protected List getAssets(final Resource resource, List mimeTypes) {
        final List assets = getAssets(resource);
        return filterResources(assets, mimeTypes);
    }

    // Filters the assets based on the mime type of the original asset
    private List filterResources(List assetResources, List mimeTypes) {
        final List result = new ArrayList();
        if (assetResources == null || assetResources.isEmpty()) {
            return result;
        }
        if (mimeTypes == null || mimeTypes.isEmpty()) {
            return assetResources;
        }
        final Iterator assetsIterator = assetResources.iterator();
        while (assetsIterator.hasNext()) {
            Resource assetRes = assetsIterator.next();
            String assetReference = productAssetManager.getReferencedAsset(assetRes.getPath());
            Asset asset = mapToAsset(assetReference);
            if (asset != null && mimeTypes.contains(asset.getMimeType())) {
                result.add(assetRes);
            }
        }
        return result;
    }

    private Asset mapToAsset(String assetReference) {
        if (StringUtils.isEmpty(assetReference)) {
            return null;
        }
        try {
            // decode fileReference (e.g. if it contains "%20" for whitespaces)
            URLCodec urlCodec = new URLCodec();
            // encode first to convert any GB18030 characters for safe decoding
            assetReference = urlCodec.encode(assetReference);
            assetReference = urlCodec.decode(assetReference);
        } catch (EncoderException e) {
            log.error("Error while encoding fileReference: {}", assetReference);
        } catch (DecoderException e) {
            log.error("Error while decoding fileReference: {}", assetReference);
        }
        Resource assetRes = resource.getResourceResolver().getResource(assetReference);
        if (assetRes == null) {
            return null;
        }
        return DamUtil.resolveToAsset(assetRes);
    }

    protected List getImages(final Resource resource) {
        final List result = new ArrayList();
        final List images = getAssets(resource, IMG_MIME_TYPE);
        if (images == null || images.isEmpty()) {
            return result;
        }
        final Iterator imagesIterator = images.iterator();
        while (imagesIterator.hasNext()) {
            Resource assetRes = imagesIterator.next();
            result.add(new ImageResource(assetRes));
        }
        return result;
    }

    @Override
    public  T getProperty(String name, Class type) {
        // See if there's an immediate value:
        ValueMap properties = resource.getValueMap();
        if (properties.containsKey(name)) {
            return properties.get(name, type);
        }

        // Else check pim reference or local inheritance:
        Resource ancestor;
        if (properties.containsKey(PN_PRODUCT_DATA)) {
            String productDataPath = properties.get(PN_PRODUCT_DATA, String.class);
            ancestor = resource.getResourceResolver().getResource(productDataPath);
            if (ancestor == null) {
                log.warn("Product data not found at [{}].", productDataPath);
                return null;
            }
        } else {
            ancestor = resource.getParent();
        }
        return (new ComponentInheritanceValueMap(ancestor)).getInherited(name, type);
    }

    @Override
    public  T getProperty(String name, String selectorString, Class type) {
        if (StringUtils.isNotEmpty(selectorString)) {
            T selectorSpecificValue = getProperty(name + "." + selectorString, type);
            if (selectorSpecificValue != null) {
                return selectorSpecificValue;
            }
        }
        return getProperty(name, type);
    }

    @Override
    public Iterator getVariants() throws CommerceException {
        return getVariants(null);
    }

    @Override
    public boolean axisIsVariant(String axis) {
        Iterator axes = getVariantAxes();
        while (axes.hasNext()) {
            if (axes.next().equals(axis)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Iterator getVariants(VariantFilter filter) throws CommerceException {
        List variants = new ArrayList();
        collectVariants(getBaseProduct(), filter, variants);
        return variants.iterator();
    }

    @Override
    public Iterator getVariantAxes() {
        String[] axes = getProperty(PN_PRODUCT_VARIANT_AXES, String[].class);
        if (axes != null) {
            return Arrays.asList(axes).iterator();
        } else {
            return new ArrayList().iterator();
        }
    }

    @Override
    public Product getBaseProduct() throws CommerceException {
        if (baseProduct == null) {
            if (!isAProductOrVariant(resource)) {
                log.error("Node isn't a product: " + resource.getPath());
                return null;
            }
            Resource tempResource = resource;
            while (isAVariant(tempResource)) {
                tempResource = tempResource.getParent();
            }
            if (isABaseProduct(tempResource)) {
                CommerceService service = tempResource.adaptTo(CommerceService.class);
                baseProduct = service.getProduct(tempResource.getPath());
            } else {
                log.error("Variant product node [{}] didn't have a product parent.", resource.getPath());
                return null;
            }
        }
        return baseProduct;
    }

    @Override
    public Product getPIMProduct() throws CommerceException {
        String productDataPath = resource.getValueMap().get(PN_PRODUCT_DATA, String.class);
        if (StringUtils.isNotEmpty(productDataPath)) {
            Resource productData = resource.getResourceResolver().getResource(productDataPath);
            if (productData == null) {
                log.warn("Product data not found at [{}].", productDataPath);
                return null;
            } else {
                return productData.adaptTo(Product.class);
            }
        }
        return null;
    }

    /**
     * Returns true if resource's cq:commerceType is "variant".
     * @param resource
     * @return
     */
    public static boolean isAVariant(Resource resource) {
        return resource.getValueMap().get(PN_PRODUCT_TYPE, "").equals("variant");
    }

    /**
     * Returns true if resource's cq:commerceType is "product".
     * @param resource
     * @return
     */
    public static boolean isABaseProduct(Resource resource) {
        return resource.getValueMap().get(PN_PRODUCT_TYPE, "").equals("product");
    }

    /**
     * Returns true if resource's cq:commerceType is  "product" or "variant".
     * @param resource
     * @return
     */
    public static boolean isAProductOrVariant(Resource resource) {
        String commerceType = resource.getValueMap().get(PN_PRODUCT_TYPE, "");
        return commerceType.equals("product") || commerceType.equals("variant");
    }

    //
    // Recursive descent of the resource tree, checking any leaf nodes against the
    // filter for inclusion.
    //
    protected void collectVariants(Product p, VariantFilter filter, List variants) {
        boolean isLeaf = true;
        Iterator i = resource.getResourceResolver().listChildren(p.adaptTo(Resource.class));
        while (i != null && i.hasNext()) {
            Resource child = i.next();
            if (isAVariant(child)) {
                isLeaf = false;
                collectVariants(child.adaptTo(Product.class), filter, variants);
            }
        }
        if (isLeaf) {
            if (filter == null || filter.includes(p)) {
                variants.add(p);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public  AdapterType adaptTo(Class type) {
        if (type == Resource.class) {
            return (AdapterType)resource;
        } else if(type == InheritanceValueMap.class) {
            return (AdapterType) new ComponentInheritanceValueMap(resource);
        }

        AdapterType ret = super.adaptTo(type);
        if (ret == null) {
            ret = resource.adaptTo(type);
        }

        return ret;
    }

    //-------------< Object overrides >-------------------------------------------------------

    // enable comparing 2 products based on their paths

    @Override
    public boolean equals(final Object obj) {
        return obj instanceof Product && ((Product) obj).getPath().equals(getPath());
    }

    @Override
    public int hashCode() {
        return getPath().hashCode();
    }

}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy