Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*************************************************************************
*
* 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.io.PrintWriter;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import aQute.bnd.annotation.ConsumerType;
import com.adobe.cq.commerce.api.CommerceConstants;
import com.day.cq.commons.jcr.JcrConstants;
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.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.osgi.PropertiesUtil;
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;
import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.launcher.ConfigEntry;
import com.adobe.granite.workflow.launcher.WorkflowLauncher;
import com.day.cq.commons.jcr.JcrObservationThrottle;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.tagging.InvalidTagFormatException;
import com.day.cq.tagging.TagManager;
/**
* An abstract base class used for writing commerce importers.
*/
@Component(componentAbstract = true, metatype = true)
@Service
@ConsumerType
public abstract class AbstractImporter {
private static final Logger log = LoggerFactory.getLogger(AbstractImporter.class);
@Reference
EventAdmin eventAdmin = null;
/**
* The (approximate) number of nodes to create in a single batch before saving the session.
*
* Configurable via the "Save Batch Size" property of the concrete OSGi component. Defaults to 1,000.
*/
private int SAVE_BATCH_SIZE;
private static final int DEFAULT_SAVE_BATCH_SIZE = 1000;
@Property(label = "Save Batch Size", description = "Approximate number of nodes to batch between session saves",
intValue = DEFAULT_SAVE_BATCH_SIZE)
public static final String SAVE_BATCH_SIZE_PROP_NAME = "cq.commerce.importer.savebatchsize";
/**
* The (approximate) number of nodes to create before pausing to allow the observation manager to catch up.
*
* Configurable via the "Throttle Batch Size" property of the concrete OSGi component. Defaults to 50,000.
*/
private int THROTTLE_BATCH_SIZE;
private static final int DEFAULT_THROTTLE_BATCH_SIZE = 50000;
@Property(label = "Throttle Batch Size", description = "Approximate number of nodes between pauses for observation manager",
intValue = DEFAULT_THROTTLE_BATCH_SIZE)
public static final String THROTTLE_BATCH_SIZE_PROP_NAME = "cq.commerce.importer.throttlebatchsize";
/**
* The (approximate) maximum number of paths included in each PRODUCT_PAGE_ADDED, PRODUCT_PAGE_MODIFIED or
* PRODUCT_PAGE_DELETED event.
*/
private int EVENT_BATCH_SIZE = 1000;
/**
* The number of messages allowed in the response.
* Large imports generate too many messages for the browser to render in reasonable time, but individual
* messages can be useful when testing on smaller imports.
*
* Configurable via the "Message Cap" property of the concrete OSGi component. Defaults to 1,000. Set
* to 0 to return only summary messages.
*/
private int MESSAGE_CAP;
private static final int DEFAULT_MESSAGE_CAP = 1000;
@Property(label = "Message Cap", description = "Maximum number of messages to return in response",
intValue = DEFAULT_MESSAGE_CAP)
public static final String MESSAGE_CAP_PROP_NAME = "cq.commerce.importer.messagecap";
private int saveBatchCount = 0;
private int throttleBatchCount = 0;
private JcrObservationThrottle throttle = null;
private Set disabledWorkflows = new HashSet();
private Map> eventQueues = new HashMap>();
private List messages;
private int errorCount;
private String tickerToken = null;
private String tickerMessage;
private boolean tickerComplete;
@Activate
protected void activate(ComponentContext ctx) throws Exception {
SAVE_BATCH_SIZE = PropertiesUtil.toInteger(ctx.getProperties().get(SAVE_BATCH_SIZE_PROP_NAME), DEFAULT_SAVE_BATCH_SIZE);
THROTTLE_BATCH_SIZE = PropertiesUtil.toInteger(ctx.getProperties().get(THROTTLE_BATCH_SIZE_PROP_NAME), DEFAULT_THROTTLE_BATCH_SIZE);
MESSAGE_CAP = PropertiesUtil.toInteger(ctx.getProperties().get(MESSAGE_CAP_PROP_NAME), DEFAULT_MESSAGE_CAP);
}
/**
* Execute the main body of the importer. This method sets up the store itself, and brackets the concrete
* implementation's doImport method with performance-enhancing facilities.
* @param resourceResolver
* @param basePath
* @param storeName
* @param incrementalImport Indicates import should be applied to existing store as a delta
* @param provider the commerceProvider to be used for the store
*/
protected void run(ResourceResolver resourceResolver, String basePath, String storeName, boolean incrementalImport, String provider) {
messages = new ArrayList();
errorCount = 0;
try {
Node rootNode = setupStore(resourceResolver, basePath, storeName, !incrementalImport, provider);
disableWorkflows(resourceResolver);
openThrottle(rootNode);
doImport(resourceResolver, rootNode, incrementalImport);
} catch (Exception e) {
log.error("Error while running import", e);
} finally {
tickerComplete = true;
checkpoint(resourceResolver.adaptTo(Session.class), true);
closeThrottle();
reenableWorkflows(resourceResolver);
}
}
/**
* Initializes the store, creating it if necessary, and deleting any existing content if not.
* @param resourceResolver
* @param basePath
* @param storeName
* @param provider
* @param clear true if any existing products should be cleared from the store
* @return
*/
protected Node setupStore(ResourceResolver resourceResolver, String basePath, String storeName, boolean clear, String provider) {
Session session = resourceResolver.adaptTo(Session.class);
String storePath = basePath;
if (StringUtils.isNotEmpty(storeName)) {
storePath += "/" + mangleName(storeName);
}
Resource rootResource = resourceResolver.getResource(storePath);
Node rootNode = null;
try {
if (rootResource != null) {
rootNode = rootResource.adaptTo(Node.class);
if (clear && rootNode.hasNodes()) {
NodeIterator it = rootNode.getNodes();
while (it.hasNext()) {
it.nextNode().remove();
}
}
} else {
rootNode = JcrUtil.createPath(storePath, false, "sling:Folder", "sling:Folder", session, false);
if (StringUtils.isNotEmpty(storeName)) {
// In many cases mangleName() will have done serious damage to the given name (particularly if
// it was multi-byte); the least we can do is set the title to the correct name:
rootNode.setProperty(JcrConstants.JCR_TITLE, storeName);
}
}
rootNode.setProperty(CommerceConstants.PN_COMMERCE_PROVIDER, provider);
session.save();
} catch (Exception e) {
log.error("Failed to initialize store: ", e);
}
return rootNode;
}
/**
* Run the actual import. Implementation to be supplied by concrete classes.
* @param resourceResolver
* @param storeRoot
* @param incrementalImport Indicates import should be applied to existing store as a delta
* @throws RepositoryException
* @throws IOException
*/
protected abstract void doImport(ResourceResolver resourceResolver, Node storeRoot, boolean incrementalImport)
throws RepositoryException, IOException;
//
// Performance considerations for large imports.
// 1) only save session every SAVE_BATCH_SIZE nodes
// 2) pause to allow observation manager to catch up every THROTTLE_BATCH_SIZE nodes
//
protected void openThrottle(Node storeRoot) throws RepositoryException {
throttle = new JcrObservationThrottle(JcrUtil.createUniqueNode(storeRoot, "temp", "nt:unstructured", storeRoot.getSession()));
throttle.open();
}
protected void closeThrottle() {
if (throttle != null) {
throttle.close();
}
}
/**
* Should be called after each node creation. The flush parameter can be used to force session saving and
* event dispatch (for instance at the end of an import), otherwise saves and event dispatch are batched.
* @param session
* @param flush
*/
protected void checkpoint(Session session, boolean flush) {
saveBatchCount++;
throttleBatchCount++;
if (saveBatchCount > SAVE_BATCH_SIZE || flush) {
if (StringUtils.isNotEmpty(tickerToken)) {
try {
Node node = JcrUtils.getOrCreateByPath("/tmp/commerce/tickers/import_" + tickerToken, "nt:unstructured", session);
node.setProperty("message", tickerMessage);
node.setProperty("errorCount", errorCount);
node.setProperty("complete", tickerComplete);
} catch (Exception e) {
log.error("ERROR updating ticker", e);
}
}
try {
session.save();
saveBatchCount = 0;
} catch (Exception e) {
logMessage("ERROR saving session", false); // pass false for isError as we'll bump the error count by hand
errorCount += saveBatchCount; // error will affect all products/variations not yet saved
log.error("ERROR saving session", e);
}
if (throttleBatchCount > THROTTLE_BATCH_SIZE) {
try {
long wait = throttle.waitForEvents();
throttleBatchCount = 0;
} catch (RepositoryException e) {
// keep calm and carry on...
}
}
}
for (String eventName : eventQueues.keySet()) {
Set paths = eventQueues.get(eventName);
if (paths.size() > EVENT_BATCH_SIZE || (flush && paths.size() > 0)) {
if (eventAdmin != null) {
Dictionary eventProperties = new Hashtable();
eventProperties.put("paths", paths.toArray(new String[paths.size()]));
eventAdmin.postEvent(new Event(eventName, eventProperties));
}
paths.clear();
}
}
}
//
// Importers may also choose to disable some workflows while importing, either for performance
// reasons or for process reasons. Simply override the predicate and return true for those
// workflows that should be disabled.
//
protected boolean disableWorkflowPredicate(ConfigEntry workflowConfigEntry) {
return false;
}
protected void disableWorkflows(ResourceResolver resourceResolver) {
try {
WorkflowLauncher launcher = resourceResolver.adaptTo(WorkflowLauncher.class);
Iterator entries = launcher.getConfigEntries();
while (entries.hasNext()) {
ConfigEntry entry = entries.next();
if (entry.isEnabled() && disableWorkflowPredicate(entry)) {
entry.setEnabled(false);
launcher.editConfigEntry(entry.getId(), entry);
disabledWorkflows.add(entry.getId());
}
}
} catch (WorkflowException e) {
log.error("Error while disabling workflows", e);
}
}
protected void reenableWorkflows(ResourceResolver resourceResolver) {
try {
WorkflowLauncher launcher = resourceResolver.adaptTo(WorkflowLauncher.class);
Iterator entries = launcher.getConfigEntries();
while (entries.hasNext()) {
ConfigEntry entry = entries.next();
if (disabledWorkflows.contains(entry.getId())) {
entry.setEnabled(true);
launcher.editConfigEntry(entry.getId(), entry);
}
}
} catch (WorkflowException e) {
log.error("Error while re-enabling workflows", e);
}
}
//
// Some utility routines.
//
protected static String mangleName(String name) {
return StringUtils.isEmpty(name) ? "" : JcrUtil.createValidName(name.trim().replace(" ", "-"));
}
protected void createMissingTags(ResourceResolver resourceResolver, String[] tags) {
TagManager tm = resourceResolver.adaptTo(TagManager.class);
for (String tag : tags) {
try {
if (tm.canCreateTag(tag)) {
tm.createTag(tag, null, null, false);
}
} catch (InvalidTagFormatException e) {
log.error("Invalid tag ID", e);
}
}
}
protected void logEvent(String eventName, String path) {
Set paths = eventQueues.get(eventName);
if (paths == null) {
paths = new HashSet();
eventQueues.put(eventName, paths);
}
paths.add(path);
}
protected void updateLoggedEvents(String oldPath, String newPath) {
for (String eventName : eventQueues.keySet()) {
Set paths = eventQueues.get(eventName);
if (paths.remove(oldPath)) {
paths.add(newPath);
}
}
}
protected void logMessage(String message, boolean isError) {
if (messages.size() < MESSAGE_CAP) {
messages.add(message);
}
if (isError) {
errorCount++;
}
}
protected int getErrorCount() {
return errorCount;
}
protected void initTicker(String tickerToken, Session session) {
this.tickerToken = tickerToken;
tickerMessage = "";
tickerComplete = false;
}
protected void updateTicker(String tickerMessage) {
this.tickerMessage = tickerMessage;
}
protected void respondWithMessages(SlingHttpServletResponse response, String summary) throws IOException {
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter pw = response.getWriter();
pw.println("");
pw.println("
");
pw.println(summary);
if (MESSAGE_CAP > 0) {
pw.println("");
for (String msg : messages) {
pw.println(msg);
}
if (messages.size() == MESSAGE_CAP) {
pw.println("...");
}
}
pw.println("