Wednesday, February 21, 2018

How to extend the OOB Adobe Experience Manager(AEM) Hybris connector

How to extend the OOB Adobe Experience Manager(AEM) Hybris connector

This video explains the details on extending the OOB Adobe Experience Manager(AEM) Hybris connector with custom functionalities




Saturday, February 17, 2018

Approach to implement content preview environment in Adobe Experience Manager(AEM)

Approach to implement content preview environment in Adobe Experience Manager(AEM)


By default Adobe Experience Manager(AEM) not provides the preview instance to preview the content before publishing to live environment but most of the cases preview is required before publishing the content to live environment.

This post explain the approach to preview and approve the content before publishing to live environment.

The below diagram explains the preview flow

AEM_Preview_environment


  • Author creates the content in production author and start the workflow
  • Workflow sends the content to Stage author and Publishers
  • Workflow assign the task to approver group and send the notification email
  • Approver reviews the content in stage environment and approve or reject the content (workflow)t - provides the detailed information for rejection
  • if the content is approved 
    • Workflow sends the approved content to production publishers.
  • If the content is rejected
    • Workflow assign the task to author and sends the notification
    • Author review the details and completes the workflow
    • Start the process again by correcting the content

Define Replication process step with ECMA script:


Create ecma file replicate.ecma under /etc/workflow/scripts/<<application>>  e.g. /etc/workflow/scripts/blog - This ecma script helps to replicate the content to stage environments


var workflowData = workItem.getWorkflowData();
var path = workflowData.getPayload().toString();
var session = workflowSession.getSession();

var replicator = sling.getService(Packages.com.day.cq.replication.Replicator);
var options = new Packages.com.day.cq.replication.ReplicationOptions();

for(var i=0;i<args.length;i++) {
    var agent= args[i];
    var filter = new Packages.com.day.cq.replication.AgentIdFilter(agent);
    options.setFilter(filter);
    options.setSuppressStatusUpdate(true);
    try {
        replicator.replicate(session, Packages.com.day.cq.replication.ReplicationActionType.ACTIVATE, path, options);
} catch(e) {
    log.error("Cannot replicate page : " + path +"on publisher. (agentId: " + agent + "): " + e);
}
}

AEM_preview_environment_approver



Saturday, January 27, 2018

How to integrate Adobe Experience Manager(AEM) and SAP Hybris through OOB connector

How to integrate Adobe Experience Manager(AEM) and SAP Hybris through OOB connector


This post explains the detailed steps for AEM and Hybris integration through OOB connector. AEM comes with default Hybris connector and sample demo site(should be downloaded via package manager), the steps covered in the post is for integrating AEM 6.3 with Hybris 6.1 but the same steps works for other AEM and Hybris versions with minimal changes.

AEM Configuration:


Install JDK 8
Install AEM 6.3 server
Install cq-geometrixx-all-pkg-5.10.68.zip, cq-geometrixx-hybris-content-6.3.2.zip and cq-hybris-content-6.3.2.zip packages from package share (I am considering geo-metrixx for demo, we.retail should be the recommended one in AEM 6.3)

aem-hybris-integration-packages

Change the Hybris server version in "Day CQ Commerce Hybris Configuration", ignore to default value if the specified version is not listed.

aem-hybris-integration-hybris-version

Change the Hybris OAuth endpoint in "Hybris OAuth Handler" based on the Hybris version, /authorizationserver/oauth/token for Hybirs 6 and later and /rest/oauth/token for earlier Hybris versions   - This step can be ignored for AEM 6.3 as the connectes defaults the values based on the Hybris version selected in the previous step.

aem-hybris-integration-OAuth-url

If the OAuth URL is not configured with correct value then the below exception will be displayed in error log

com.adobe.cq.commerce.hybris.impl.OAuthHandler Server did not respond with 2xx -> authentication failed.



Saturday, January 20, 2018

How to use Re-Fetching Flush Agent in Adobe Experience Manager(AEM)

This tutorial explains the details on AEM re-fetching flush agent and how to use to flush the content through re-fetching flush agent.

Re-Fetching Flush Agent


With regular flush agent, the agent flush the cache and the flushed content would only be retrieved from the publisher again after first time requested. This will create issue if the multiple parallel requests hit the dispatcher after flush as the new page is not yet cached in the dispatcher.



The re-fetching flush agent in AEM make sure the pages are requested and cached on the dispatcher immediately after replication.

Using a re-fetching flush agent allows to pre-populate the cache as it instructs the dispatcher to retrieve the pages from publish instance immediately after the flush occurs. Re-fetching flush agent works same as normal flush agent in dispatcher, additionally dispatcher sends a GET request for the content paths in the flush requests to fetch the content and update the cache.



Re-fetching flush is slow when activating lot of pages, enable the re-fetching only for specific content that is frequently accessed and activated.

The re-fetching feature is available in dispatcher since version 4.0.9

Configure Re-Fetching Flush agent


As a first step, develop a content builder that will send the flush URL’s into the flush request part of request body. The flush agent uses POST method to invoke dispatcher flush.

The sample content builder bundle can be downloaded from the below URL

https://github.com/techforum-repo/bundles/tree/master/refetching-flush-agent

The agent bundle is tested with AEM as Cloud Service Development Instance with aem-sdk-api dependency, the same can be modified to use uber-jar dependency.

Install the bundle to publisher as configuring the flush agent in the publish server is recommended— mvn clean install -PautoInstallBundle -Daem.port=4503

Now new serialization type “Re-fetch Dispatcher flush” is enabled while configuring the agents.


Define a re-fetching flush agent in publisher with required details, the process is same as normal flush agent with some additional configurations

In Settings tab select the “Serialization Type” as “Re-Fetch Dispatcher Flush”

In Extended tab add POST as the HTTP Method for sending the request to Dispatcher






In AEM Cloud Service, content is published using Sling Content Distribution . The replication agents used in previous versions of AEM are no longer used or provided, configure the required forward agent configurations to distribute the changes.

Refer the below URL for details on configuring forward distribution

https://medium.com/@techforum/sling-content-distribution-in-aem-part-1-35158614dc53

Now distribute/activate a page and verify the Re-Fetching Flush Agent, the activated page is updated in dispatcher before first time requesting the page.



The re-fetching flush agent will helps to improve the performance of the frequently used critical pages, be cautious while using this feature as so many page activation will impact the AEM performance.



Friday, January 19, 2018

Serving the static resources through different domains(Cookie less Domains) - Adobe Experience Manager(AEM)

Serving the static resources through different domains(Cookie less Domains) - Adobe Experience Manager(AEM) 


This post will explain the approach to improve the page load time of the websites through serving static resources via multiple cookie less domain.

The page load time of the website directly related to the number of resources requested to render the page in browser, most of the cases the browser makes multiple calls to receive the related resources.

The browser is restricted with number of default simultaneous connections per server, if the number of resources requested in the same domain is more then that will delay the page load time - the resources are loaded sequentially(this restriction is more for HTTP 1.1 but HTTP 2 protocol support parallel downloading). This issue can be addressed by distributing the static resources across multiple domains that will intern increase the parallel download of resources.

The static resource to be on a cookie-less domain that makes the content load faster. The Cookies are uploaded with every request on a domain although they are only required on dynamic pages. This will add additional overhead while requesting the static resources through a cookie aware domain and increases the page load time - loading the cookie to request and download the cookie from the response.

To avoid this problem as discussed above define multiple domains and those are cookie less.

To define a cookie less domain, create a multiple domains e.g static1.example.com, static2.example.com etc and CNAME points to the parent domain - Make sure the cookie is set in the server only specific to the parent domain.

In Adobe Experience Manager(AEM) this can be achieved in the below two approaches

Component level:


Changing the static resource URL's in individual components with newly defined domains based on the resource type - may be separate domain for images, scripts, css etc. This will required more effort and also every component level changes to assign the specific URL's for the static resources. There is a possibly the developer will not implement this in all the components and the resources will be served from main domain.


Sling Rewriter to change the static resource URL's:


Define a Static Resource Transformer that will rewrite the resource URL's with static domains defined - multiple domains can be used

The ACS Static Reference Rewriter can be used to change the static resource URL's - https://adobe-consulting-services.github.io/acs-aem-commons/features/utils-and-apis/static-reference-rewriter/index.html

I have defined a Static Resource rewriter based on the above one to add some additional functionalities to match our requirements, thought of sharing as this may help someone with same requirement.

  • Exclude the rewrite based on attribute name and values
  • Exclude the rewrite for external resources
  • Exclude based on the complete path and URL prefix 
  • Rewrite the URL's for all srcset URL's 
  • The Rewriter is invoked only for specific content path.

StaticResourceTransformFactory.java

import java.io.IOException;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.rewriter.ProcessingComponentConfiguration;
import org.apache.sling.rewriter.ProcessingContext;
import org.apache.sling.rewriter.Transformer;
import org.apache.sling.rewriter.TransformerFactory;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.apache.felix.scr.annotations.ConfigurationPolicy;

@Component(
label = "Static Resources Transformer Factory",
description = "Static Resources Transformer Factory",
        metatype = true,policy = ConfigurationPolicy.REQUIRE)
@Service
@Properties({
    @Property(
            name = "pipeline.type", label = "Static Resources Transformer Pipeline Type",description ="Static Resources Transformer Pipeline Type"),
    @Property(
            name = "webconsole.configurationFactory.nameHint",
            value = "Static Resources Transformer: {pipeline.type}")
})

public final class StaticResourceTransformFactory implements TransformerFactory {
    public final class StaticResourceTransformer extends org.apache.cocoon.xml.sax.AbstractSAXPipe
    implements org.apache.sling.rewriter.Transformer {

    @Override
    public void startElement(String uri, String localName, String qname, Attributes attr) throws SAXException {

      final String[] includeElementAttributesArray =includeElementAttributes.get(localName);
    AttributesImpl attrs = new AttributesImpl(attr);
   
    boolean excludeAttributeValue=false;
   
    for (int i = 0; i < attrs.getLength(); i++) {
    final String[] excludeAttributeValuesArray = excludeAttributeValues.get(attrs.getLocalName(i));
    excludeAttributeValue=ArrayUtils.contains(excludeAttributeValuesArray, attrs.getValue(i));
    LOG.info("excludeAttributeValue:"+excludeAttributeValue);

    if(excludeAttributeValue)
    {
    break;
    }
    }

    for (int i = 0; i < attrs.getLength(); i++) {
    String name = attrs.getLocalName(i);
    String value = attrs.getValue(i);
   
    if (ArrayUtils.contains(includeElementAttributesArray, name)
    && !ArrayUtils.contains(excludePath, value) && !isExcludedPrefix(excludePrefix,value)
    &&!(value.startsWith("https") || value.startsWith("http") || value.startsWith("//")) && !excludeAttributeValue) {
    {
    if(name.equals("srcset"))
    {
    String[] srcset=value.split(",");
    String srcsetValue="";
    for(int j=0;j<srcset.length;j++)
    {
    if(!(value.startsWith("https") || value.startsWith("http") || value.startsWith("//")))
    {
    srcsetValue=!srcsetValue.equals("")?srcsetValue+","+ prependHostName(srcset[j].trim()):srcsetValue+prependHostName(srcset[j].trim());
    }else
    {
    srcsetValue=srcsetValue+","+srcset[j];
    }
    }
    attrs.setValue(i, srcsetValue);
   
    }else
    {
    attrs.setValue(i, prependHostName(value));
    }
    }
    }
    }

    super.startElement(uri, localName, qname, attrs);

    }