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

com.adobe.cq.commerce.pim.common.AbstractProductImporter 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.pim.common;

import java.io.IOException;
import java.util.Calendar;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.servlet.ServletException;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aQute.bnd.annotation.ConsumerType;
import com.adobe.cq.commerce.api.conf.CommerceBasePathsService;
import com.adobe.cq.commerce.pim.api.ProductImporter;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.i18n.I18n;

import static com.adobe.cq.commerce.api.CommerceConstants.PN_COMMERCE_TYPE;
import static com.day.cq.commons.jcr.JcrConstants.JCR_LASTMODIFIED;

/**
 * An abstract base class used for writing product importers.
 */
@Component(componentAbstract = true, metatype = true)
@Service
@ConsumerType
public abstract class AbstractProductImporter extends AbstractImporter implements ProductImporter {
    private static final Logger log = LoggerFactory.getLogger(AbstractProductImporter.class);

    /**
     * The location where imported product stores are created.
     */
    protected String basePath;

    /**
     * The maximum number of children allowed to be imported into a single section before
     * buckets are created.  (Also the maximum allowed in each bucket.)
     *
     * Configurable via the "Bucket Size" property of the concrete OSGi component.  Defaults to 500.
     */
    private int BUCKET_MAX;

    private static final int DEFAULT_BUCKET_SIZE = 500;

    @Property(label = "Bucket Size", description = "Maximum products per section before bucketing, and maximum in each bucket",
            intValue = DEFAULT_BUCKET_SIZE)
    public static final String BUCKET_SIZE_PROP_NAME = "cq.commerce.productimporter.bucketsize";

    /**
     * The node name used when buckets are required.
     * Conceptually a constant, but implemented without static or final in case subclasses want
     * to override.
     */
    protected String NN_BUCKET = "bucket";

    /**
     * The node type used when buckets are required.
     * Conceptually a constant, but implemented without static or final in case subclasses want
     * to override.
     */
    protected String NT_BUCKET = "sling:Folder";

    private int productCount;                         // number of product nodes created
    private int variationCount;                       // number of product variation nodes created

    @Activate
    protected void activate(ComponentContext ctx) throws Exception {
        super.activate(ctx);
        BUCKET_MAX = PropertiesUtil.toInteger(ctx.getProperties().get(BUCKET_SIZE_PROP_NAME), DEFAULT_BUCKET_SIZE);

        // We cannot inject the service with @Reference because it doesn't work in classes extending this class
        BundleContext btx = ctx.getBundleContext();
        ServiceReference ref = btx.getServiceReference(CommerceBasePathsService.class.getName());
        CommerceBasePathsService cbps = (CommerceBasePathsService) btx.getService(ref);

        basePath = cbps.getProductsBasePath();

        btx.ungetService(ref);
    }

    public void importProducts(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();

        if (!validateInput(request, response)) {
            return;
        }

        final I18n i18n = new I18n(request);
        ResourceResolver resourceResolver = request.getResourceResolver();
        Session session = resourceResolver.adaptTo(Session.class);
        String storeName = request.getParameter("storeName");
        String storePath = request.getParameter("storePath");
        String provider = request.getParameter("provider");
        initTicker(request.getParameter("tickertoken"), session);

        Boolean incrementalImport = false;
        if (request.getParameter("incrementalImport") != null) {
            incrementalImport = true;
        }

        productCount = 0;
        variationCount = 0;
        run(resourceResolver, storePath != null ? storePath : basePath, storeName, incrementalImport, provider);

        long millis = System.currentTimeMillis() - startTime;
        long seconds = millis / 1000;
        if (seconds > 120) {
            log.info("Imported " + productCount + " products in " + seconds / 60 + " minutes.");
        } else {
            log.info("Imported " + productCount + " products in " + seconds + " seconds.");
        }

        String summary = i18n.get("{0} products and {1} variants created/updated.","0 is product count and 1 is variation count, both numbers",productCount,variationCount);
        if (getErrorCount() > 0) {
            summary += " " + i18n.get("{0} errors encountered.","0 is number of errors",getErrorCount());
        }
        respondWithMessages(response, summary);
    }

    /**
     * Validate any input required to run the importer.  Implementation to be supplied by concrete classes.
     */
    protected abstract boolean validateInput(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException;

    private void demoteProductChildrenToBucket(Node parent, Session session) throws RepositoryException {
        Node bucket = JcrUtil.createUniqueNode(parent, NN_BUCKET, NT_BUCKET, session);
        NodeIterator children = parent.getNodes();
        long productCount = 0;
        while (children.hasNext()) {
            Node child = (Node) children.next();
            if (child.hasProperty("cq:commerceType") && child.getProperty("cq:commerceType").getString().equals("product")) {
                String oldPath = child.getPath();
                String newPath = JcrUtil.copy(child, bucket, child.getName()).getPath();
                child.remove();
                updateLoggedEvents(oldPath, newPath);

                productCount++;
            }
        }
        bucket.setProperty("cq:importCount", productCount);
        parent.setProperty("cq:importCount", (Value) null);
    }

    /**
     * Creates a product node at a given absolute path.  Intervening nodes will be created as sling:Folder,
     * as will any bucket nodes required to honor BUCKET_MAX (unless NT_BUCKET has been overridden).
     *
     * 

Updates the message and event queues; saves the session if the save batch size has been reached.

*/ protected Node createProduct(String path, Session session) throws RepositoryException { // // Fetch or create parent: // String parentPath = Text.getRelativeParent(path, 1); Node parent = JcrUtil.createPath(parentPath, false, "sling:Folder", "sling:Folder", session, false); // // If we're already bucketing, get the current bucket: // boolean bucketing = false; if (parent.hasProperty("cq:importBucket")) { parent = parent.getNode(parent.getProperty("cq:importBucket").getString()); bucketing = true; } // // Will our addition overflow the parent or current bucket? // long count = parent.hasProperty("cq:importCount") ? parent.getProperty("cq:importCount").getLong() + 1 : 1; if (count > BUCKET_MAX) { // // If we haven't yet started bucketing, then we need to demote all the existing products to an // initial bucket. // if (!bucketing) { demoteProductChildrenToBucket(parent, session); } else { parent = parent.getParent(); } // // Now create a new bucket and register it in the parent: // Node bucket = JcrUtil.createUniqueNode(parent, NN_BUCKET, NT_BUCKET, session); parent.setProperty("cq:importBucket", bucket.getName()); parent = bucket; count = 1; } parent.setProperty("cq:importCount", count); // // Now carry on creating the product: // Node product = JcrUtil.createUniqueNode(parent, Text.getName(path), "nt:unstructured", session); product.setProperty(PN_COMMERCE_TYPE, "product"); product.setProperty("sling:resourceType", "commerce/components/product"); product.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()); productCount++; logEvent("com/adobe/cq/commerce/pim/PRODUCT_ADDED", product.getPath()); logMessage("Created product " + product.getPath(), false); updateTicker(makeTickerMessage()); checkpoint(session, false); return product; } protected String makeTickerMessage() { return productCount + " products imported/updated"; } /** * Should be called after updating a product. * *

Updates the message and event queues; saves the session if the save batch size has been reached.

*/ protected void productUpdated(Node product) throws RepositoryException { productCount++; logEvent("com/adobe/cq/commerce/pim/PRODUCT_MODIFIED", product.getPath()); logMessage("Updated product " + product.getPath(), false); updateTicker(makeTickerMessage()); checkpoint(product.getSession(), false); } /** * Should be called after deleting a product. * * *

Updates the message and event queues; saves the session if the save batch size has been reached.

*/ protected void productDeleted(Node product) throws RepositoryException { logEvent("com/adobe/cq/commerce/pim/PRODUCT_DELETED", product.getPath()); logMessage("Deleted product " + product.getPath(), false); updateTicker(makeTickerMessage()); checkpoint(product.getSession(), false); } /** * Creates a variation with a given name within a given product. * *

Updates the message and event queues; saves the session if the save batch size has been reached.

*/ protected Node createVariant(Node parentProduct, String name) throws RepositoryException { Node variant = JcrUtil.createUniqueNode(parentProduct, name, "nt:unstructured", parentProduct.getSession()); variant.setProperty(PN_COMMERCE_TYPE, "variant"); variant.setProperty("sling:resourceType", "commerce/components/product"); variant.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()); variationCount++; Node baseProduct = getBaseProduct(parentProduct); if (baseProduct != null) { logEvent("com/adobe/cq/commerce/pim/PRODUCT_MODIFIED", baseProduct.getPath()); } logMessage("Created variation " + variant.getPath(), false); checkpoint(parentProduct.getSession(), false); return variant; } /** * Should be called after updating a variant. * *

Updates the message and event queues; saves the session if the save batch size has been reached.

*/ protected void variantUpdated(Node variant) throws RepositoryException { variationCount++; Node baseProduct = getBaseProduct(variant); if (baseProduct != null) { logEvent("com/adobe/cq/commerce/pim/PRODUCT_MODIFIED", baseProduct.getPath()); } logMessage("Updated variation " + variant.getPath(), false); updateTicker(makeTickerMessage()); checkpoint(variant.getSession(), false); } /** * Creates a commerce/components/product/image image node for a given product or variation node. It is the * caller's responsibility to set any properties on the returned image (such as the fileReference). * *

Updates the message and event queues; saves the session if the save batch size has been reached.

*/ protected Node createImage(Node product) throws RepositoryException { Node image = product.addNode("image", "nt:unstructured"); image.setProperty("sling:resourceType", "commerce/components/product/image"); image.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()); Node baseProduct = getBaseProduct(product); if (baseProduct != null) { logEvent("com/adobe/cq/commerce/pim/PRODUCT_MODIFIED", baseProduct.getPath()); } logMessage("Created image " + image.getPath(), false); checkpoint(product.getSession(), false); return image; } /** * Returns the base product node of a product or variation. */ protected Node getBaseProduct(Node node) throws RepositoryException { while (node != null && !node.getProperty(PN_COMMERCE_TYPE).getString().equals("product")) { node = node.getParent(); } return node; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy