Saturday, December 15, 2018

Different approaches to dynamically include custom scripts into websites - Adobe Experience Manager(AEM)

Different approaches to include custom scripts dynamically into websites - Adobe Experience Manager(AEM)


Sometimes we may need to include scripts dynamically into the websites with out changing the code, this post will explain the different approaches to include the scripts dynamically in the websites without performing the code changes.

Tag Manager:


Inject the custom scripts remotely through tag manager systems, this will provide better management of tags without changing the code.

GTM(Google Tag Manager) - Refer https://www.albinsblog.com/2018/12/how-to-include-dynamic-custom-scripts-website-gtm.html for details on injecting the custom script through GTM.

AdobeLaunch-Refer https://www.albinsblog.com/2018/12/how-to-include-dynamic-custom-scripts-adobe-launch-aem.html for details on injecting the custom script through Adobe Launch.

AEM Cloud configuration:


This approach uses the custom AEM Cloud Configuration to include the dynamic scripts - header and footer to the website

Define a Cloud configuration to enable the header and footer scripts and attach the cloud configuration to the required websites to inject the custom dynamic scripts.

Steps-

In CRXDE Lite, create a new node under /apps:

Name: utilities
Type: nt:folder

Create 2 new nodes under /apps/utilities:

Name: components
Type: sling:Folder
and
Name: templates
Type: sling:Folder

Right click on /apps/utilities/components, create a new component genericscriptpage

Label - genericscriptpage
Title - genericscriptpage

group - .hiddenSuperType-cq/cloudserviceconfigs/components/configpage

Add the below additional properties

cq:defaultView - html
allowedParents - utilities/templates/genericscript

Remove the default genericscriptpage.jsp and create content.jsp file under /apps/utilities/components/genericscriptpage with the following content

<%@page session="false" 
  contentType="text/html"
  pageEncoding="utf-8"%><%
%><%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %><%

%><cq:defineObjects/>
<div>
    <h3>General Script Settings</h3>   
    <ul>
<li><div class="li-bullet"><strong>Head Script: </strong><br><%= xssAPI.encodeForHTML(properties.get("headScript", "")).replaceAll("\\&\\#xa;","<br>") %></div></li>
<li><div class="li-bullet"><strong>Foot Script: </strong><br><%= xssAPI.encodeForHTML(properties.get("footScript", "")).replaceAll("\\&\\#xa;","<br>") %></div></li>
    </ul>
</div>

dynamic-script-aem


Create a new node under/apps/utilities/components/genericscriptpage/:

Name: dialog
Type: cq:Dialog

Properties:

title - Generic Script Configuration
xtype - dialog

Create a new node under/apps/utilities/components/genericscriptpage/dialog:

Name: items
Type: cq:WidgetCollection

Create a new node under /apps/utilities/components/genericscriptpage/dialog/items:

Name: panel
Type: cq:Panel

Properties:

title - Script Configuration
xtype - panel

Create a new node under /apps/utilities/components/genericscriptpage/dialog/items/panel:

Name: items
Type: cq:WidgetCollection

Create a new node headScript under /apps/utilities/components/genericscriptpage/dialog/items/panel/items
Name: headScript
Type: cq:widget

Properties:

fieldLabel - Head Script
name - ./headScript
xtype - textarea

Create a new node footScript under /apps/utilities/components/genericscriptpage/dialog/items/panel/items

Name: footScript
Type: cq:widget

Properties:

fieldLabel - Foot Script
name - ./footScript
xtype - textarea

Create a new component genericscript under /apps/utilities/components

Label - genericscript
Title - genericscript

Remove the default file genericscript.jsp, create a file headScript.jsp and add the below content 

<%--

--%><%@page session="false" 
import="org.apache.sling.api.resource.Resource,
                org.apache.sling.api.resource.ValueMap,
                org.apache.sling.api.resource.ResourceUtil,
                com.day.cq.wcm.webservicesupport.Configuration,
                com.day.cq.wcm.webservicesupport.ConfigurationManager" %><%
%><%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %><%
%><cq:defineObjects/><%

String[] services = pageProperties.getInherited("cq:cloudserviceconfigs", new String[]{});
ConfigurationManager cfgMgr = resource.getResourceResolver().adaptTo(ConfigurationManager.class);
if(cfgMgr != null) {
String scriptCode = null;
Configuration cfg = cfgMgr.getConfiguration("generic-script", services);
if(cfg != null) {
scriptCode = cfg.get("headScript", null);
}

if(scriptCode != null) {

%><%= scriptCode %><%

}
}
%>


Create a file footScript.jsp with following content under /apps/utilities/components/genericscript 

<%--

--%><%@page session="false" 
import="org.apache.sling.api.resource.Resource,
                org.apache.sling.api.resource.ValueMap,
                org.apache.sling.api.resource.ResourceUtil,
                com.day.cq.wcm.webservicesupport.Configuration,
                com.day.cq.wcm.webservicesupport.ConfigurationManager" %><%
%><%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %><%
%><cq:defineObjects/><%

String[] services = pageProperties.getInherited("cq:cloudserviceconfigs", new String[]{});
ConfigurationManager cfgMgr = resource.getResourceResolver().adaptTo(ConfigurationManager.class);
if(cfgMgr != null) {
String scriptCode = null;
Configuration cfg = cfgMgr.getConfiguration("generic-script", services);
if(cfg != null) {
scriptCode = cfg.get("footScript", null);
}

if(scriptCode != null) {

%><%= scriptCode %><%

}
}
%>
dynamic-script-aem




How to include dynamic custom script to websites through Adobe Launch - Adobe Experience Manager(AEM)

How to include dynamic custom script to websites through Adobe Launch - Adobe Experience Manager(AEM)

Launch, by Adobe is a next-generation tag management system that unifies the client-side marketing ecosystem by empowering developers to build integrations on a robust, extensible platform that partners, clients, and the broader industry can build on and contribute to.

Adobe Launch is the upgraded version of Adobe’s Dynamic Tag Manager (DTM), Adobe’s Dynamic Tag Manager (DTM) will be replaced by Adobe Launch(It doesn’t mean the platform will be shut down anytime soon).

This post explains the approach to use Adobe Launch to include the dynamic scripts as part of Adobe Experience Manager(AEM) websites.

Login to Adobe Launch and create new property- LAUNCH_CUSTOM_SCRIPT, specify the domain as localhost.local for localhost testing and Save the Property.

Adobe_Launch_aem_integration

Define New Rule:


Adobe_Launch_aem_integration

Configure the Event

Adobe_Launch_aem_integration

Configure the Action

Adobe_Launch_aem_integration

Define the script in Editor

Adobe_Launch_aem_integration

var s = document.createElement("script");
s.innerHTML = "alert('Custom Script from GTM');"; // add the custom script content
  //s.src="test.js";// specify the script file instead of script content
document.head.appendChild(s);

Adding multi line script - example

s.innerHTML = "document.onreadystatechange = function(){ "+
    "if(document.readyState=='loaded' || document.readyState=='complete') "+
        "alert('test');"+
"}";



How to include dynamic custom script to websites through GTM(Google Tag Manager) - Adobe Experience Manager(AEM)

How to include dynamic custom script to websites through GTM(Google Tag Manager) - Adobe Experience Manager(AEM)


Google Tag Manager is a tag management system that allows you to quickly and easily update tracking codes and related code fragments collectively known as "tags" on your website or mobile app. Once a small segment of Tag Manager code has been added to your project, you can easily configure and deploy your tag configurations from a web-based user interface without the need to deploy additional code in most cases. This reduces errors and frees you from having to involve a developer every time you need to make changes.

This post explains the approach to use Google Tag Manager(GTM) to include the dynamic scripts as part of Adobe Experience Manager(AEM) websites.


Create a free google tag manager account if one is not available

tagmanager-GTM


Install the GTM header and body scripts to the AEM website

tagmanager-GTM


tagmanager-GTM


Copy the head script to the header file and body script to body footer(start of body section)

tagmanager-GTM

tagmanager-GTM

tagmanager-GTM




Thursday, December 6, 2018

How to use Attribute Loader in Adobe Search and Promote

How to use Attribute Loader in Adobe Search and Promote


This post explains the details on Search and Promote Attribute Loader.

Attribute loader help us to provide additional meta data to the URL's crawled from website.

For example, the PDF's document crawled from the website will not have any additional metadata specified but the additional metadata can be loaded through Attribute Loader.

e.g while crawling the pdf document from website it will be possible to provide only pdf URl and file name but will not be able to provide the additional details like title, description etc, these additional metadatas can be provided via Attribute Loader.

The values will be merged during indexing through primary key value.

PDF URL Crawled from  website - https://www.example.com/test/Albin.pdf

Attribute Loader Data-

url- https://www.example.com/test/Albin.pdf(primary key)
Tittle - test PDF
Description - test PDF

The Attribute Loader is executed before actual indexing and the metadata data values are merged based on the primary key during indexing.

AttributeLoader

Defining Attribute Loader:


AttributeLoader

Sample Feed XML

<attributes xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
    <channel>
        <title>Attribute Loader Feed</title>
        <Item>
            <title>test PDF1</title>
            <desc>test PDF1</desc>
            <url>https://www.example.com/test/Albin1.pdf</url>
        </Item>
        <Item>
            <title>test PDF2</title>
            <desc>test PDF2</desc>
            <url>https://www.example.com/test/Albin2.pdf</url>
        </Item>
    </channel>
</attributes>
AttributeLoader

AttributeLoader


Tuesday, December 4, 2018

Search&Promote – Crawling(IndexConnector)

Search&Promote – Crawling(IndexConnector)


IndexConnector:


Enable to define additional input sources for indexing XML pages or any kind of feed

The IndexConnector can be used to index the product data from ecommerce systems with large number of product data to reduce the crawling and indexing time. IndexConnector approach better crawling/indexing performance.

An XML data source consists of XML records, that contain information that corresponds to individual documents that can be added to the index

A text data feed contains individual new-line-delimited records that correspond to individual documents that can be added to the index

Mapping can be defined, how each record's items are used to populate the metadata fields in the resulting index

Multiple protocols can be used to connect to the input sources from IndexConnecter – HTTP(S)/FTP/SFTP/FILE

IndexConnector

The IndexConnector is not enabled by default in S&P account, the same should be enabled by Adobe S&P account team.

Defining IndexConnector:


IndexConnector

IndexConnector2.png

Sample product feed file(XML)

<feed
    xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
    <channel>
        <title>Product Feed</title>
        <Item>
            <link>https://www.example.com/product-title/p/123</link>
            <title>
                <![CDATA[product-title]]>
            </title>
            <pubDate>05/09/2011</pubDate>
            <pubYear>2011</pubYear>
            <description>
                <![CDATA[<p>product description</p>]]>
            </description>
            <productType>Research</productType>
            <category>
                <![CDATA[Financial Planning|Financial Planners|Research]]>
            </category>
            <ProductId>123</ProductId>
            <imageUrl>/content/dam/Images/product/123.jpg</imageUrl>
        </Item>
        <Item>
            <link>https://www.example.com/product-title/p/1234</link>
            <title>
                <![CDATA[product-title]]>
            </title>
            <pubDate>05/09/2011</pubDate>
            <pubYear>2011</pubYear>
            <description>
                <![CDATA[<p>product description</p>]]>
            </description>
            <productType>Research</productType>
            <category>
                <![CDATA[Financial Planning|Financial Planners|Research]]>
            </category>
            <ProductId>1234</ProductId>
            <imageUrl>/content/dam/Images/product/1234.jpg</imageUrl>
        </Item>
        <Item>
            <link>https:/www.example.com/product-title/p/12345</link>
            <title>
                <![CDATA[product-title]]>
            </title>
            <pubDate>05/09/2011</pubDate>
            <pubYear>2011</pubYear>
            <description>
                <![CDATA[<p>product description</p>]]>
            </description>
            <productType>Research</productType>
            <category>
                <![CDATA[Financial Planning|Financial Planners|Research]]>
            </category>
            <ProductId>12345</ProductId>
            <imageUrl>/content/dam/Images/product/12345.jpg</imageUrl>
        </Item>
    </channel>
</feed>


Monday, September 17, 2018

How to Enable Custom Validation on multifield Touch UI - Adobe Experience Manager(AEM)

How to Enable Custom Validation on multifield Touch UI - Adobe Experience Manager(AEM)


This post explain the details on enabling Custom Validation on multifield Touch UI.

Define the Touch UI dialog for the component, the XML structure of the dialog is below

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="63 Collapsible Multifield"
    sling:resourceType="cq/gui/components/authoring/dialog"
    extraClientlibs="[touchmulti.email.validation]">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
        <items jcr:primaryType="nt:unstructured">
            <column
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/container">
                <items jcr:primaryType="nt:unstructured">
                    <products
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                        composite="{Boolean}true"
                        eaem-show-on-collapse="EAEM.showProductName"
                        fieldLabel="Products">
                        <field
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/container"
                            name="./products">
                            <items jcr:primaryType="nt:unstructured">
                                <column
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <product
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                            fieldDescription="Name of Product"
                                            fieldLabel="Product Name"
                                            name="./product"/>
                                        <email
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/foundation/form/textfield"
                                            fieldLabel="Email"
                                            name="./email"/>
                                    </items>
                                </column>
                            </items>
                        </field>
                    </products>
                </items>
            </column>
        </items>
    </content>
</jcr:root>

touch_ui_multifield_dialog


Enable the validation for Email:


The form should not be submitted if there is atleast one error with email.
Error field should be highlighted with different 

Define a cq:ClientLibraryFolder node under the component, name it clientlibs and add the following properties.

categories (String[]) - <define category name> e.g touchmulti.email.validation

touch_ui_multifield_clientlibs_validation


Saturday, September 15, 2018

Improve the performance of the Adobe Experience Manager(AEM) websites

Improve the performance of the Adobe Experience Manager(AEM) websites


This post explains my experience on improving the Adobe Experience Manager(AEM) website performance

"Cache as much as possible" - CDN layer:


The caching is the important thing to be considered for improving the performance, in AEM setup the dispatcher will be used for caching the static content.
The CDN can be added on top of dispatcher to distributed caching to support the caching in different region to provide better performance. As the CDN is distributed at least by region the user will be served from nearby region to improve the performance. in this setup publishers will be only receive the initial request and subsequent request will be served by CDN and dispatcher.

There are multiple CDN options like Akamai and AWS Cloud Front. The request flow diagram below

network_flow_aem

Cache-Control max-age header:


Specify the cache control header with required max-age value to control the amount of time the files are cached by browser.

Add higher max-age values for static resources so that the browser caching can be used optimaly for bettwr performance.

The max-age can be be added as part of the virtual host configuration. e.g

<filesMatch ".(css|js|)$">
Header set Cache-Control "max-age=2628000"
</filesMatch>

<filesMatch ".(jpg|jpeg|png|gif|html|ico)$">
Header set Cache-Control "max-age=900"
</filesMatch>


Versioning of CSS and JS files:


The performance gain is achieved through browser caching static files for specified time(max-age). The browser cache busting is important to update the modified static files in browser, say the browser has the CSS file cached for one month and you want to change the CSS. You need a strategy for breaking the cache and forcing the browser to download a new copy of the CSS.

Change the version number of the static files upon modification so that the browser cache will be updated with new file irrespective of the max-age configuration. 

The versioning of static resources can be enabled in AEM through "ACS Commons Versioned Clientlibs" - https://adobe-consulting-services.github.io/acs-aem-commons/features/versioned-clientlibs/index.html



Wednesday, September 12, 2018

java.lang.UnsupportedOperationException: Deserialization not allowed for class [Ljava.lang.Object; - Adobe Experience Manager(AEM)

java.lang.UnsupportedOperationException: Deserialization not allowed for class [Ljava.lang.Object; - Adobe Experience Manager(AEM)

We were receiving the following exception while deserializing the objects in AEM

java.lang.UnsupportedOperationException: Deserialization not allowed for class com.test.Test; (on Wed Sep 12 16:32:50 CDT 2018)
        at org.kantega.notsoserial.DefaultNotSoSerial.preventDeserialization(DefaultNotSoSerial.java:256)
        at org.kantega.notsoserial.DefaultNotSoSerial.onBeforeResolveClass(DefaultNotSoSerial.java:248)
        at org.kantega.notsoserial.ObjectInputStreamClassVisitor.onBeforeResolveClass(ObjectInputStreamClassVisitor.java:48)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1819)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
        at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1874)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1529)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)

The issue is due to the class name was not white listed in "Deserialization Firewall Configuration",Deserialization Firewall  help as to mitigation the deserialization attacks in Java
It gives you complete control over which classes your application should be allowed to deserialize.

deserialization_firewall_aem
The error got changed to the below one after white listing the custom package name in "Deserialization Firewall Configuration".

java.lang.UnsupportedOperationException: Deserialization not allowed for class [Ljava.lang.Object; (on Wed Sep 12 16:32:50 CDT 2018)
        at org.kantega.notsoserial.DefaultNotSoSerial.preventDeserialization(DefaultNotSoSerial.java:256)
        at org.kantega.notsoserial.DefaultNotSoSerial.onBeforeResolveClass(DefaultNotSoSerial.java:248)
        at org.kantega.notsoserial.ObjectInputStreamClassVisitor.onBeforeResolveClass(ObjectInputStreamClassVisitor.java:48)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1819)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
        at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1874)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1529)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
        at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2231)
        at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2155)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2013)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)

The class name "[Ljava.lang.Object" should be white listed as the deserialization type is array of custom objects, the "Deserialization Firewall Configuration" already enabled with default value "[" so that all the arrray type should be white listed.

In our case unfortunately the default "[" value was removed from the configuration and due to that the serialization for array types are rejected. The issue got resolved after adding the default value "["

Please make sure the default value "["  is not removed from white list to support array type.

deserialization_firewall_aem

This is for my reference but i am happy if this help someone.


Thursday, September 6, 2018

Adobe Experience Manager(AEM) On-Premises to Adobe Managed Service(AMS) Cloud Migration

Adobe Experience Manager(AEM) On-Premises to Adobe Managed Service(AMS) Cloud Migration


This post explains the different things that should be considered while migrating On-Premises Adobe Experience Manager(AEM) platform to the AWS cloud managed through AMS.

AMS cloud migration provides lot of benefits -

  • Extend the server capacity based on the demand
  • Quick spinning up of new servers
  • Less management and initial setup cost
  • Better security and monitoring of platform
  • Streamlined process
  • Higher availability

We have to consider this option based on the how much control we require on the production environment - AMS environments will be restricted for client access.

AMS_deployment_model

Below are some of the important items need attention while migrating the On-Premises AEM platform to AMS Cloud.

Deployment options:


There is different deployment options available based on the SLA

AMS_deployment_by_SLA



Tuesday, September 4, 2018

How to use Luke(Lucene Index Toolbox) to analyze Lucene Index in AEM(Adobe Experience manager)

How to use Luke(Lucene Index Toolbox) to analyze Lucene Index in AEM(Adobe Experience manager)


This post will explain the details on analyzing the created Lucene index in AEM(Adobe Experience Manager)

Retrieve the Lucene Index:


By default in Oak the Lucene Index files are stored in NodeStore and will not be accessible directly but if the following configurations("Enable CopyOnRead" or "Enable CopyOnWrite" ) are enabled in "Apache Jackrabbit Oak LuceneIndexProvider" the Lucene Index will be copied to Local files system path, If the "Local index storage path" not specified then indexes would be stored under 'index' dir under Repository Home (localIndexDir)

Lucene_index_provider

Index_on_local_file_system_aem

Index_on_local_file_system_aem

If the index is copied to loacl file system this can be directly accessed,the index is stored in the file name starts with segments

Index_on_local_file_system_aem
The mapping between local path and the index can be found here - localhost:4502/system/console/jmx/org.apache.jackrabbit.oak%3Aname%3DIndexCopier+support+statistics%2Ctype%3DIndexCopierStats(this URL will available only if the above mentioned properties are enabled)

Index_status_oak_aem


The below steps can be be followed to retrieve the indexing files if the index files are not stored in local.

Download oak-run-x.x.x.jar that corresponds to AEM Oak version, the AEM Oak version can be identified from CRXDE(the oak-run version 1.4.1 was not working and i downloaded 1.8.0 version - https://repository.apache.org/service/local/artifact/maven/redirect?r=releases&g=org.apache.jackrabbit&a=oak-run&v=1.8.0)

Execute java -jar oak-run-1.8.0.jar index <Node Store Path> e.g. java -jar oak-run-1.8.0.jar index C:\Albin\Development\AEM\6.2\crx-quickstart\repository\segmentstore to identify the available indexes, the index stats and index definitions under the folder from where the command is executed

The index is stored in the file name starts with segments


oak_run_available_index

The indexing status and the definitions can be accessed from the following URL also - http://localhost:4502/system/console/jmx/org.apache.jackrabbit.oak%3Aname%3DLucene+Index+statistics%2Ctype%3DLuceneIndex



Friday, August 17, 2018

How to Enable Cross Origin Resource Sharing(CORS) support in AEM(Adobe Experience Manager)

How to Enable Cross Origin Resource Sharing(CORS) support in AEM(Adobe Experience Manager)


Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.

Sometimes we may have scenarios to expose the AEM services to be invoked from cross origins, this post explains the different approaches to enable the Cross Origin Resource Sharing Supports.

The CORS support is introduced OOB in AEM 6.3 version, refer https://helpx.adobe.com/experience-manager/kt/platform-repository/using/cors-security-article-understand.html for more details.


Enabling CORS for older AEM versions(<6.3)


The request is failed with below exception in browser console while trying to invoke cross origin(service hosted in different domains) services through Ajax.

"Failed to load http://localhost:4503/bin/sampleJSONPService: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access".

There are two approaches to enable the Cross Origin Requests

  • JSONP
  • CORS Headers

JSONP


JSONP stands for JSON with Padding, JSONP is a method for sending JSON data without worrying about cross-domain issues.
JSONP supports only the GET request method, JSONP works on legacy browsers.

Enable the service to rerun the JSON response with Padding


@SuppressWarnings("serial")
@SlingServlet(
   selectors = "json",
   paths = "/bin/sampleJSONPService",
   methods = "GET")
public class SimpleServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {
    resp.setHeader("Content-Type", "application/json");
    resp.setHeader("X-Content-Type-Options", "");
    String callback=req.getParameter("callback");
       
    JSONObject response = new JSONObject();
    try {
response.put("test1", "testvalue1");
response.put("test2", "testvalue2");
response.put("test3", "testvalue3");
response.put("test4", "testvalue4");
} catch (JSONException e) {
e.printStackTrace();
}
   
    if(callback!=null && !callback.trim().equals("")) {
    resp.getWriter().write( callback + "("+response.toString()+ ")");
    }else
    {
    resp.getWriter().write(response.toString());
    }   
   
    }
}

The above service return the JSON response with padding

Invoke the JSONP service from Ajax


<html>
<head>
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="  crossorigin="anonymous">
</script>
</head>
<body>

Test Data (<span class="testData">Default</span>)

<script>
var dataUrl = 'http://localhost:4503/bin/sampleJSONPService.json?callback=?';

$.ajax({
url: dataUrl,
dataType: 'jsonp',
success: function(data) {
jQuery('.testData').html('').text(data.test1)
}});
</script>
</body>
</html>


This will provide the response without any issue

test({"test1":"testvalue1","test2":"testvalue2","test3":"testvalue3","test4":"testvalue4"})


CORS Headers


Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. CORS also supports other types(other than GET) of HTTP requests, CORS is supported by most modern web browsers.

The Access-Control-Allow-Origin response header indicates whether the response can be shared with resources with the given origin.

e.g.
Access-Control-Allow-Origin "*"   supports the Cross Origin requests from all domains
Access-Control-Allow-Origin "https://example.com" supports Cross Origin requests only from https://example.com

Enabling Access-Control-Allow-Origin header


The header can be enabled directly in the service response or thorough apache configuration

Though Service response - Add Access-Control-Allow-Origin header with required value in the service response


@SuppressWarnings("serial")
@SlingServlet(
   selectors = "json",
   paths = "/bin/sampleJSONPService",
   methods = "GET")
public class SimpleServlet extends SlingSafeMethodsServlet {
    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {


Monday, July 9, 2018

How to display custom site specific error(404,403 and 500) pages in multi site Adobe Experience Manager(AEM) setup?

How to display custom site specific error(404,403 and 500) pages in multi site Adobe Experience Manager(AEM) setup?


This post explains the approach to handle custom site specific error handling for Adobe Experience Manager(AEM) sites in a multi site environment

Sling Model to identify the error page:


Create a Sling model ErrorHandlerRequestModel.java in core module withe the below code - This will help to identify the site specific error page path specific to the error code.

package com.errorhandler.core.models;

import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

@Model(adaptables = SlingHttpServletRequest.class)
public class ErrorHandlerRequestModel {

    private static final String DEFAULT_ERROR_PAGE = "/content/geometrixx-outdoors";
    private static final String ERROR_CODE_404 = "404";
    private static final int MAX_DEPTH = 2;
    private static final String PATH_SEPERATOR = "/";

    @Self
    private SlingHttpServletRequest slingRequest;

    @Inject
    @Default(values = ERROR_CODE_404)
    private String errorCode;

    private String pagePath;
 
    @PostConstruct
    protected void init() {
    pagePath = DEFAULT_ERROR_PAGE + errorCode;
        final String requestURI = slingRequest.getRequestPathInfo().getResourcePath();
        if (requestURI!=null && !requestURI.equals("")) {
          pagePath = getErrorPageFromRequestedUrl(errorCode, requestURI);
    }
    }

    private String getErrorPageFromRequestedUrl(final String errorCode, final String requestURI) {
        final Page resolvedPage = getPageFromPath(requestURI);
        if (resolvedPage != null) {
            return getErrorPathFromPage(errorCode, resolvedPage);
        }
        return null;
    }

    private Page getPageFromPath(String requestURI) {
        final PageManager pageManager = slingRequest.getResourceResolver().adaptTo(PageManager.class);
        while (requestURI.contains(PATH_SEPERATOR)) {
            Page page = pageManager.getContainingPage(requestURI);
            if (page != null) {
                return page;
            } else {
                requestURI = requestURI.substring(0, requestURI.lastIndexOf(PATH_SEPERATOR));
            }
        }
        return null;
    }

    private String getErrorPathFromPage(final String errorCode, final Page resolvedPage) {
    if (resolvedPage.hasChild(errorCode)) {
        return resolvedPage.getPath() + PATH_SEPERATOR + errorCode;
        }
        if (resolvedPage.getParent() != null && resolvedPage.getDepth() >= MAX_DEPTH) {
            return getErrorPathFromPage(errorCode, resolvedPage.getParent());
        }
        return null;
    }

    public String getPagePath() {
        return pagePath;
    }
}

Configure the DEFAULT_ERROR_PAGE(the content folder in which the default 404, 403 and 500 error pages are available) in the above code  - the error pages that will be displayed if the the site specific 404, 403 and 500 pages are missing.
Add the below instruction into core module pom.xml - to notify the package in which the sling models are available for registration

<Sling-Model-Packages>com.errorhandler.core.models</Sling-Model-Packages>


<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>                   
<Import-Package>javax.inject;version=0.0.0,*</Import-Package>
<Sling-Model-Packages>com.errorhandler.core.models</Sling-Model-Packages>
</instructions>
</configuration>
</plugin>

Configure Error Handlers:


Create 403.html, 404.html and Exception.html(error handler to handle all the Internal Server Exceptions) under /apps/sling/servlet/errorhandler

aem-error-handler-scripts


Add the below contents to 403.html, 404.html and Exception.html respectively

403.html

<sly data-sly-use.errorPage="${'com.errorhandler.core.models.ErrorHandlerRequestModel'@ errorCode='403'}"/>
<sly data-sly-resource="${errorPage.pagePath}.html"/>

404.html

<sly data-sly-use.responseStatus="apps.sling.servlet.errorhandler.ResponseStatus">
    <sly data-sly-use.errorPage="${'com.errorhandler.core.models.ErrorHandlerRequestModel'@ errorCode='404'}"/>
    <sly data-sly-resource="${errorPage.pagePath}.html"/>
</sly>

Exception.html

<sly data-sly-use.errorPage="${'com.errorhandler.core.models.ErrorHandlerRequestModel'@ errorCode='500'}"/>
<sly data-sly-resource="${errorPage.pagePath}.html"/>


Create ResponseStatus.java under /apps/sling/servlet/errorhandler and add the below content

package apps.sling.servlet.errorhandler;

import com.adobe.cq.sightly.WCMUse;

public class ResponseStatus extends WCMUse {
 
    @Override
    public void activate() throws Exception {
        getResponse().setStatus(404);
    }
}


Configure Error Pages:


Create site specific error files 403, 404 and 500(the files can be created in any level - the minimum depth should be 2 i.e under /content/sitename), the files will be taken based on the level where the exception occurred.

aem-site-specific-error-pages


Exception directly under /content/geometrixx-outdoors consider the error pages under /content/geometrixx-outdoors
Exception under /content/geometrixx-outdoors/en consider the error pages under /content/geometrixx-outdoors/en

Configure the local error pages - 404, 403 and 500 for all the required sites.

Now the user will be displayed the corresponding error file content based on the error code.


This implementation has been tested in AEM 6.2 version.


Monday, July 2, 2018

HTTPS URL is resulting with 404 - Adobe Experience Manager

HTTPS URL is resulting with 404 - Adobe Experience Manager


Recently, we have faced the issue with https URL's, the URL's with masked path (/en/test.html) is not working with https protocol.

However, the unmasked (/content/site/en/test.html) URL is working, also http URL is working without any issues.

Based on our analysis, it looks to be the system is not honoring the /etc/map.publish/https mapping for https request and due to that the content path(without full path - /en/test.html) is not accessible and 404 is displayed

Our case the request is https from browser and the Load Balancer terminate the SSL and forward the request to dispatcher - Load Balancer notify the dispatcher that the initial request is https via header X-Forwarded-Proto (this header value differs based on the load balancer)

The dispatcher send the request to publisher with required headers and publisher consider the request as https based on the above header and match the Resource Mapping accordingly - /etc/map.publish/https

The 404 will be displayed for masked URL's if publisher not able to match the /etc/map.publish/https for incoming request.

How to resolve?


Option1:


  • Match the SSL Filter settings to those expected from the entity where SSL is terminated (Load Balancer). You can check these values forwarded in the dispatcher.log file and make sure they match to those of the SSL Filter. Configure the SSL Filter (Apache Felix Http Service SSL Filter) in Publisher with SSL forward header and value
SSL_Filter_https_aem

  • Allow the following headers in dispatcher farm file /clientheaders section, if /clientheaders section is not set to allow all

          X-Forwarded-Proto (this header value change based on the Load balancer)
               - other known values X-FORWARDED-SSL, X-Forwarded-Protocol and Front-End-Https
          X-Forwarded-Port
  • White list the SSL headers mentioned above in CDN, if CDN is enabled in the flow.

Option2:


Disallow the SSL forward headers in dispatcher farm file /clientheaders section; disallow the following header - X-Forwarded-Proto in /clientheaders section, review the "Apache Felix Http Service SSL Filter" in publisher to identify the exact header value used to identify the forwarded SSL request.

This allow the publisher to consider the request as http and match the /etc/map.publish/http node for incoming requests. 

In cases the SSL is terminated at the Web server, follow the below steps:


At the bottom of the httpd.conf add the following configuration: RequestHeader set X-Forwarded-Port "-1"

Allow the X-Forwarded-Port header in dispatcher farm file if /clientheaders section is not set to allow all 


Wednesday, June 13, 2018

Resolving the issues while migrating the packages between Adobe Experience Manager instances(AEM)

Resolving the issues while migrating the packages between Adobe Experience Manager instances(AEM)


This is a common scenario to migrate the packages between AEM instances, some times we may face issues while building or uploading the big packages.

This post explains the details to resolve the issues observed while building or uploading the big packages between AEM instances

java.io.IOException: No space left on device while building the package:


The "java.io.IOException: No space left on device" error will be thrown while building big packages in AEM server without having sufficient space available in /tmp folder.

java.io.IOException: No space left on device
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.util.zip.DeflaterOutputStream.deflate(DeflaterOutputStream.java:253)
at java.util.zip.DeflaterOutputStream.write(DeflaterOutputStream.java:211)
at java.util.zip.ZipOutputStream.write(ZipOutputStream.java:331)
at org.apache.commons.io.output.ProxyOutputStream.write(ProxyOutputStream.java:90)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1793)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1769)
at org.apache.commons.io.IOUtils.copy(IOUtils.java:1744)
at org.apache.jackrabbit.vault.fs.io.JarExporter.writeFile(JarExporter.java:128)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:216)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:214)
at org.apache.jackrabbit.vault.fs.io.AbstractExporter.export(AbstractExporter.java:184)
at org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl.assemble(PackageManagerImpl.java:142)
at org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl.assemble(PackageManagerImpl.java:96)
at org.apache.jackrabbit.vault.packaging.impl.JcrPackageManagerImpl.assemble(JcrPackageManagerImpl.java:594)
at org.apache.jackrabbit.vault.packaging.impl.JcrPackageManagerImpl.assemble(JcrPackageManagerImpl.java:574)
at com.day.crx.packaging.impl.J2EEPackageManager.consoleBuild(J2EEPackageManager.java:291)
at com.day.crx.packaging.impl.J2EEPackageManager.doPost(J2EEPackageManager.java:176)
at com.day.crx.packaging.impl.PackageManagerServlet.doPost(PackageManagerServlet.java:156)

no_space_left_on_device_aem_package



Wednesday, May 30, 2018

Exceptions/Issues while configuring SAML Authentication Handler - Adobe Experience Manager(AEM)

Exceptions/Issues while configuring SAML Authentication Handler - Adobe Experience Manager(AEM)


This post explains the Exceptions/Issues received while configuring the SAML authentication handler and the fixes to overcome the issues.

Issue1:


Problem accessing /saml_login. Reason:
com.adobe.granite.keystore.KeyStoreNotInitialisedException: Uninitialised system trust store.

uninitialized-system-trust-store


14.05.2018 11:24:39.988 *WARN* [qtp1134377453-62] org.eclipse.jetty.servlet.ServletHandler /saml_login
com.adobe.granite.keystore.KeyStoreNotInitialisedException: Uninitialised system trust store.
at com.adobe.granite.keystore.internal.KeyStoreServiceImpl.internalGetTrustStore(KeyStoreServiceImpl.java:462)
at com.adobe.granite.keystore.internal.KeyStoreServiceImpl.getTrustStore(KeyStoreServiceImpl.java:151)
at com.adobe.granite.auth.saml.SamlAuthenticationHandler.handleLogin(SamlAuthenticationHandler.java:577)
at com.adobe.granite.auth.saml.SamlAuthenticationHandler.extractCredentials(SamlAuthenticationHandler.java:348)
at org.apache.sling.auth.core.impl.AuthenticationHandlerHolder.doExtractCredentials(AuthenticationHandlerHolder.java:75)
at org.apache.sling.auth.core.impl.AbstractAuthenticationHandlerHolder.extractCredentials(AbstractAuthenticationHandlerHolder.java:60)
at org.apache.sling.auth.core.impl.SlingAuthenticator.getAuthenticationInfo(SlingAuthenticator.java:709)
at org.apache.sling.auth.core.impl.SlingAuthenticator.doHandleSecurity(SlingAuthenticator.java:461)
at org.apache.sling.auth.core.impl.SlingAuthenticator.handleSecurity(SlingAuthenticator.java:446)
at org.apache.sling.engine.impl.SlingHttpContext.handleSecurity(SlingHttpContext.java:121)
at org.apache.felix.http.base.internal.context.ServletContextImpl.handleSecurity(ServletContextImpl.java:339)
at org.apache.felix.http.base.internal.handler.ServletHandler.doHandle(ServletHandler.java:334)
at org.apache.felix.http.base.internal.handler.ServletHandler.handle(ServletHandler.java:297)
at org.apache.felix.http.base.internal.dispatch.ServletPipeline.handle(ServletPipeline.java:93)
at org.apache.felix.http.base.internal.dispatch.InvocationFilterChain.doFilter(InvocationFilterChain.java:50)
at org.apache.felix.http.base.internal.dispatch.HttpFilterChain.doFilter(HttpFilterChain.java:31)
at org.apache.sling.i18n.impl.I18NFilter.doFilter(I18NFilter.java:129)


Problem accessing /saml_login. Reason:
com.adobe.granite.keystore.KeyStoreNotInitialisedException: Uninitialised key store for user authentication-service

Uninitialised-Keystore-authentication-service



Thursday, May 3, 2018

io.jsonwebtoken,version=[0.7,1) -- Cannot be resolved - Adobe Experience Manager(AEM)

io.jsonwebtoken,version=[0.7,1) -- Cannot be resolved - Adobe Experience Manager(AEM) 

I was getting the below exception while using the io.jsonwebtoken dependency in the bundle and the bundle was in Installed state.

org.osgi.framework.BundleException: Unresolved constraint in bundle com.test [452]: Unable to resolve 452.5: missing requirement [452.5] osgi.wiring.package; (&(osgi.wiring.package=io.jsonwebtoken)(version>=0.7.0)(!(version>=1.0.0)))
at org.apache.felix.framework.Felix.resolveBundleRevision(Felix.java:4095)
at org.apache.felix.framework.Felix.startBundle(Felix.java:2114)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:977)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:964)
at org.apache.sling.installer.core.impl.tasks.BundleStartTask.execute(BundleStartTask.java:93)
at org.apache.sling.installer.core.impl.OsgiInstallerImpl.doExecuteTasks(OsgiInstallerImpl.java:847)
at org.apache.sling.installer.core.impl.OsgiInstallerImpl.executeTasks(OsgiInstallerImpl.java:689)
at org.apache.sling.installer.core.impl.OsgiInstallerImpl.run(OsgiInstallerImpl.java:265)
at java.lang.Thread.run(Unknown Source)

The below dependency is added into pom.xml

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>


Steps to fix:


Configure the maven-bundle-plugin as shown below

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>
!android.util;resolution:=optional,
!com.fasterxml.jackson.core;resolution:=optional,
!com.fasterxml.jackson.databind;resolution:=optional,
!org.bouncycastle.jce;resolution:=optional,
!org.bouncycastle.jce.spec;resolution:=optional,
javax.inject;version=0.0.0,*
</Import-Package>
<Embed-Dependency>jjwt;inline=true</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
</instructions>
    </configuration>
</plugin>


Tuesday, April 24, 2018

How to handle the request parameter encoding(charset) in Adobe Experience Manager(AEM)

How to handle the request parameter encoding(charset) in Adobe Experience Manager(AEM)

Sometimes we may need to change the default parameter encoding in Adobe Experience Manager(AEM) to handle different language characters.

This post explains the approach to change the encoding for AEM request parameters.

Apache Sling Request Parameter Handling:


The request parameter encoding can be changed through the following configuration - "Apache Sling Request Parameter Handling"

Change the value of "Default Parameter Encoding" to required values - e.g UTF-8/ISO-8859-1 (default value is ISO-8859-1)

sling_request_parameter_handling_aem

This is a global configuration and it will change the parameter encoding for all the incoming requests.

Change the encoding for specific form:


The below steps can be followed to change the encoding for specific form

Add the parameter "_charset_" as hidden field with required encoding value inside the form.

<form role="form" id="test" action="xxxxx" method="POST" accept-charset="ISO-8859-1" onsubmit="document.charset = 'ISO-8859-1'">
<input type="hidden" id="_charset_" name="_charset_" value="ISO-8859-1"/>
........
........
</form>

accept-charset="ISO-8859-1"(non IE browsers), onsubmit="document.charset = 'ISO-8859-1'"(Configuration for IE browser) - This configuration specify the encoding that is to be used for the form submission.

This will change the encoding of this particular form. The different encoding can be specified page level and form level only to handle the data.


Wednesday, April 18, 2018

How to implement extension-less URL's in Adobe Experience Manager(AEM)

How to implement extension-less URL's in Adobe Experience Manager(AEM)

As per the SEO best practices it is better to define extension less URL's to boost the ranking, AEM require the extension to understand and serve incoming request.

This post explains the approach to achieve the extension less URL in Adobe Experience Manager(AEM)

There are two steps

- Rule Configuration Dispatcher
       Remove .html extension from incoming URL with /
Append the .html while invoking the publisher for the URL's ending with /

- AEM etc/map configuration
      Reverse mapping to rewrite the html URL in the pages to extension less
      Forward mapping to map the incoming request to resource

This is tested in AEM 6.2 version

Apache configurations:


#Handle the landing page
RewriteRule ^/$ /en/ [R=301,L]
#Mask the /content/geometrixx-outdoors path
RewriteRule ^/content/geometrixx-outdoors/(.*)(\.html)?$ /$1 [NE,L,R=301]

#Replace the .html with /
RewriteCond %{REQUEST_URI} \.html$
RewriteRule ^/(.*).html$ /$1/ [R=301,L,QSA]

#Append the .html for those URL's ending with / before sending to publisher
RewriteCond %{REQUEST_URI} !^/$
RewriteRule ^/(.*)/$ /$1.html [PT,L,QSA]

Publisher etc/map configurations:


Create a node localhost.8080(replace with required DNS and port) of type sling:Mapping under /etc/map/http or /etc/map/https based on the protocol used

Add the following property

sling:internalRedirect[] - /content/geometrixx-outdoors

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="sling:Mapping"
    sling:internalRedirect="[/content/geometrixx-outdoors]">
    <redirect/>
    <reverse/>
</jcr:root>

AEM-extension-less-URL