PIMPostProcessor.java 13.4 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.adobe.cq.commerce.api.CommerceException
 *  com.adobe.cq.commerce.api.Product
 *  com.adobe.cq.commerce.api.asset.ProductAssetManager
 *  com.adobe.cq.commerce.common.AbstractJcrProduct
 *  javax.jcr.Item
 *  javax.jcr.Node
 *  javax.jcr.Property
 *  javax.jcr.RepositoryException
 *  javax.jcr.Session
 *  org.apache.commons.lang.StringUtils
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.api.SlingHttpServletRequest
 *  org.apache.sling.api.resource.Resource
 *  org.apache.sling.api.resource.ResourceResolver
 *  org.apache.sling.api.resource.ResourceResolverFactory
 *  org.apache.sling.commons.osgi.PropertiesUtil
 *  org.apache.sling.servlets.post.Modification
 *  org.apache.sling.servlets.post.ModificationType
 *  org.apache.sling.servlets.post.SlingPostProcessor
 *  org.osgi.service.component.ComponentContext
 *  org.osgi.service.event.Event
 *  org.osgi.service.event.EventAdmin
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.adobe.cq.commerce.pim.impl;

import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.Product;
import com.adobe.cq.commerce.api.asset.ProductAssetManager;
import com.adobe.cq.commerce.common.AbstractJcrProduct;
import java.util.Calendar;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.lang.StringUtils;
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.Reference;
import org.apache.felix.scr.annotations.Service;
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.ResourceResolverFactory;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.servlets.post.Modification;
import org.apache.sling.servlets.post.ModificationType;
import org.apache.sling.servlets.post.SlingPostProcessor;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(metatype=1, label="Adobe CQ Commerce PIM Post Processor", description="Updates lastMod dates after POST operations on product data.")
@Service(value={SlingPostProcessor.class})
@Property(name="service.description", value={"Updates lastMod dates after POST operations on product data"})
public class PIMPostProcessor
implements SlingPostProcessor {
    private Logger logger;
    @Reference
    private ResourceResolverFactory resolverFactory;
    @Reference
    EventAdmin eventAdmin;
    private String[] searchRoots;
    private static final String[] DEFAULT_PATHS = new String[]{"/etc/commerce/products"};
    @Property(label="Paths", description="Paths under which product data will be processed", value={"/etc/commerce/products"})
    public static final String PROPERTY_PATHS = "cq.commerce.pimpostprocessor.searchRoots";

    public PIMPostProcessor() {
        this.logger = LoggerFactory.getLogger(this.getClass());
        this.resolverFactory = null;
    }

    @Activate
    protected void activate(ComponentContext context) {
        String[] configuredPaths = PropertiesUtil.toStringArray(context.getProperties().get("cq.commerce.pimpostprocessor.searchRoots"), (String[])DEFAULT_PATHS);
        this.searchRoots = new String[configuredPaths.length];
        for (int i = 0; i < configuredPaths.length; ++i) {
            String path = configuredPaths[i];
            this.searchRoots[i] = path.endsWith("/") ? path : path + "/";
        }
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    public void process(SlingHttpServletRequest request, List<Modification> changes) throws Exception {
        ResourceResolver postResolver = request.getResourceResolver();
        Session postSession = (Session)postResolver.adaptTo(Session.class);
        ResourceResolver serviceResolver = null;
        try {
            HashMap<String, String> authenticationInfo = new HashMap<String, String>();
            authenticationInfo.put("sling.service.subservice", "backend");
            serviceResolver = this.resolverFactory.getServiceResourceResolver(authenticationInfo);
            Session serviceSession = (Session)serviceResolver.adaptTo(Session.class);
            HashMap<String, ModificationType> changedNodes = new HashMap<String, ModificationType>();
            for (Modification mod : changes) {
                switch (mod.getType()) {
                    case CREATE: {
                        this.processPath(mod.getSource(), postSession, changedNodes, ModificationType.CREATE);
                        break;
                    }
                    case MODIFY: 
                    case ORDER: {
                        this.processPath(mod.getSource(), postSession, changedNodes, ModificationType.MODIFY);
                        break;
                    }
                    case MOVE: {
                        this.processPath(mod.getSource(), serviceSession, changedNodes, ModificationType.DELETE);
                        this.processPath(mod.getDestination(), postSession, changedNodes, ModificationType.CREATE);
                        break;
                    }
                    case COPY: {
                        this.processPath(mod.getDestination(), postSession, changedNodes, ModificationType.CREATE);
                        break;
                    }
                    case DELETE: {
                        this.processPath(mod.getSource(), serviceSession, changedNodes, mod.getType());
                    }
                }
            }
            for (Map.Entry change : changedNodes.entrySet()) {
                Resource baseProduct;
                String changedPath = (String)change.getKey();
                ModificationType modificationType = (ModificationType)change.getValue();
                this.logger.debug("post-processing modification to " + changedPath);
                Resource changedResource = postResolver.getResource(changedPath);
                if (changedResource == null) {
                    changedResource = serviceResolver.getResource(changedPath);
                }
                if ((baseProduct = this.getBaseProduct(changedResource)) == null) continue;
                try {
                    Calendar now = Calendar.getInstance();
                    ProductAssetManager productAssetManager = (ProductAssetManager)postResolver.adaptTo(ProductAssetManager.class);
                    if (changedPath.equals(baseProduct.getPath())) {
                        this.generateProductEvents(baseProduct, modificationType);
                    } else if (this.deleteEmptyProductAsset(postResolver, changedResource, changes, productAssetManager)) {
                        this.generateProductEvents(baseProduct, ModificationType.MODIFY);
                    } else {
                        this.setLastModDate(postResolver, changedResource, now, ModificationType.MODIFY, changes);
                        this.deleteEmptyProductAssets(postResolver, changedResource, changes, productAssetManager);
                        this.generateProductEvents(baseProduct, ModificationType.MODIFY);
                    }
                    this.setLastModDate(postResolver, baseProduct, now, modificationType, changes);
                }
                catch (RepositoryException e) {
                    this.logger.error("failed to post-process modification to " + changedPath, (Throwable)e);
                }
            }
        }
        finally {
            if (serviceResolver != null && serviceResolver.isLive()) {
                serviceResolver.close();
            }
        }
    }

    private void processPath(String path, Session session, Map<String, ModificationType> changedNodes, ModificationType type) throws Exception {
        ModificationType existing;
        Item item;
        boolean underSearchPaths = false;
        for (String searchRoot : this.searchRoots) {
            if (!path.startsWith(searchRoot)) continue;
            underSearchPaths = true;
            break;
        }
        if (!underSearchPaths) {
            return;
        }
        Item item2 = item = session.itemExists(path) ? session.getItem(path) : null;
        while (item != null && !item.isNode()) {
            item = item.getParent();
        }
        if (item != null && (existing = changedNodes.get(item.getPath())) != ModificationType.CREATE && existing != ModificationType.DELETE) {
            changedNodes.put(item.getPath(), type);
        }
    }

    private void setLastModDate(ResourceResolver postResolver, Resource resource, Calendar now, ModificationType modificationType, List<Modification> changes) throws RepositoryException {
        if ((resource = postResolver.getResource(resource.getPath())) != null) {
            Node node = (Node)resource.adaptTo(Node.class);
            this.logger.debug("setting lastMod on:", (Object)node.getPath());
            if (modificationType == ModificationType.CREATE && !node.hasProperty("jcr:created")) {
                node.setProperty("jcr:created", now);
            } else {
                node.setProperty("jcr:lastModified", now);
            }
            changes.add(Modification.onModified((String)node.getPath()));
        }
    }

    private void deleteEmptyProductAssets(ResourceResolver postResolver, Resource productResource, List<Modification> changes, ProductAssetManager productAssetManager) throws RepositoryException {
        Product product = (Product)productResource.adaptTo(Product.class);
        if (product == null) {
            return;
        }
        List productAssets = product.getAssets();
        for (Resource productAsset : productAssets) {
            this.deleteEmptyProductAsset(postResolver, productAsset, changes, productAssetManager);
        }
    }

    private boolean deleteEmptyProductAsset(ResourceResolver postResolver, Resource productAsset, List<Modification> changes, ProductAssetManager productAssetManager) throws RepositoryException {
        if (productAsset != null) {
            productAsset = postResolver.getResource(productAsset.getPath());
        }
        if (productAsset != null && productAssetManager != null) {
            String productAssetPath = productAsset.getPath();
            String referencedAsset = productAssetManager.getReferencedAsset(productAssetPath);
            if (productAssetManager.isSupportedAsset(productAssetPath) && StringUtils.isEmpty((String)referencedAsset)) {
                try {
                    productAssetManager.removeAsset(productAssetPath);
                    changes.add(Modification.onDeleted((String)productAssetPath));
                    return true;
                }
                catch (CommerceException e) {
                    this.logger.error("Could not delete the empty product asset at: {}", (Object)productAssetPath);
                    return false;
                }
            }
        }
        return false;
    }

    private void generateProductEvents(Resource baseProduct, ModificationType modificationType) throws RepositoryException {
        String baseProductPath = baseProduct.getPath();
        Hashtable<String, String> properties = new Hashtable<String, String>();
        properties.put("path", baseProductPath);
        if (modificationType == ModificationType.CREATE) {
            this.logger.debug("posting com/adobe/cq/commerce/pim/PRODUCT_ADDED event for: " + baseProductPath);
            this.eventAdmin.postEvent(new Event("com/adobe/cq/commerce/pim/PRODUCT_ADDED", properties));
        } else if (modificationType == ModificationType.DELETE) {
            this.logger.debug("posting com/adobe/cq/commerce/pim/PRODUCT_DELETED event for: " + baseProductPath);
            this.eventAdmin.postEvent(new Event("com/adobe/cq/commerce/pim/PRODUCT_DELETED", properties));
        } else {
            this.logger.debug("posting com/adobe/cq/commerce/pim/PRODUCT_MODIFIED event for: " + baseProductPath);
            this.eventAdmin.postEvent(new Event("com/adobe/cq/commerce/pim/PRODUCT_MODIFIED", properties));
        }
    }

    protected Resource getBaseProduct(Resource resource) throws RepositoryException {
        while (resource != null && !AbstractJcrProduct.isABaseProduct((Resource)resource)) {
            resource = resource.getParent();
        }
        return resource;
    }

    protected void bindResolverFactory(ResourceResolverFactory resourceResolverFactory) {
        this.resolverFactory = resourceResolverFactory;
    }

    protected void unbindResolverFactory(ResourceResolverFactory resourceResolverFactory) {
        if (this.resolverFactory == resourceResolverFactory) {
            this.resolverFactory = null;
        }
    }

    protected void bindEventAdmin(EventAdmin eventAdmin) {
        this.eventAdmin = eventAdmin;
    }

    protected void unbindEventAdmin(EventAdmin eventAdmin) {
        if (this.eventAdmin == eventAdmin) {
            this.eventAdmin = null;
        }
    }

}