Accessing Component Policies in AEM via ResourceType-Based Servlet

How can I leverage component policies chosen at the template level to manage the dropdown-based selection?

AEM has integrated component policies as a pivotal element of the editable template feature. This functionality empowers both authors and developers to provide options for configuring the comprehensive behavior of fully-featured components, including deciding which fields to display, hide, or customize. This configuration is carried out at the template level, facilitating reuse across various templates or template-specific customizations.

Furthermore, AEM introduces a robust styling feature that empowers frontend developers to manage the visual appearance and user interface of components. This grants authors the capability to tailor the look and feel of the component.

Requirement:

In a previous discussion, we outlined a step-by-step process for Accessing Component Policies in AEM at the Component Dialog Level, Sightly code, and Sling model.

Now, consider a scenario where a company is selling various electronic devices in different countries. For example, a company like Brisket sells phones, tablets, and laptops in India, and only phones and tablets in the USA. However, if they plan to introduce laptops in the USA in the future, they’d like to make changes to the template policy. How can this be achieved at the dialog level?

Solution:

Step 1: L Create an “ElectronicsDS” component and add the design dialog with specific attributes:

ElectronicDS Design dialog

Step 2:  Create the dialog and add an extra granite node with a specific property:

ElectronicDS dialog

Step 3: When you open the component dialog and inspect the field, you will see extra attributes, including “component-path” and “field-path”.

Shows componet and feild paths

Step 4: Create a dialog-level listener that makes a resourceType based call to a Servlet using “field-path” and passes the attributes as shown.

(function($, $document, Granite, Coral) {

    var flag = false;
    var errorMessage = 'Error occurred during processing';

    $(document).on("foundation-contentloaded", function(e) {
        var value = $("coral-select[name='./country']").val();
        var fieldPath = $("coral-select[name='./devices']").attr("data-field-path");
        var compPath = $("coral-select[name='./devices']").attr("data-component-path");
        populateItems(fieldPath, compPath, value);
    });

    var REGEX_SELECTOR = "dropdown.selector",
        foundationReg = $(window).adaptTo("foundation-registry");

    foundationReg.register("foundation.validation.validator", {
        selector: "[data-foundation-validation='" + REGEX_SELECTOR + "']",
        validate: function(el) {
            if ($(el).is(":visible")) {
                var value = $(el).val();
                var fieldPath = $("coral-select[name='./devices']").attr("data-field-path");
                var compPath = $("coral-select[name='./devices']").attr("data-component-path");
                populateItems(fieldPath, compPath, value);
            }
        }
    });

    function populateItems(fieldPath, compPath, value) {
        dsUrl = Granite.HTTP.externalize(fieldPath) + ".html?componentPath=" + compPath + "&dropdownValue=" + value;
        $.ajax({
            type: "GET",
            async: false,
            url: dsUrl,
            success: function(result) {
                if (result) {
                        var select = document.querySelector("coral-select[name='./devices']");
                        var newHtml = document.createElement("div");
                        newHtml.innerHTML = result;
                        var newResult = newHtml.querySelector("coral-select[name='./devices']").children;
                        Coral.commons.ready(select, function(component) {
                            component.items.clear();
                            [...newResult].forEach((e) => {
                              component.items.add(e);
                            })
                        });
                    } else {
                        flag = true;
                    }
                }
        });
        if (flag) {
            return errorMessage;
        }
    }

}(jQuery, $(document), Granite, Coral));

Step 5: Create a registered path-based Servlet that accepts the component path as a parameter, retrieves the component policy, and returns dropdown options.

package com.aem.operations.core.servlets;

import com.adobe.granite.ui.components.ExpressionHelper;
import com.day.cq.wcm.api.policies.ContentPolicy;
import com.day.cq.wcm.api.policies.ContentPolicyManager;
import com.day.crx.JcrConstants;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.*;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import com.adobe.granite.ui.components.Config;
import com.adobe.granite.ui.components.Value;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.adobe.granite.ui.components.ExpressionResolver;
import org.osgi.service.component.annotations.Reference;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.*;

@Component(
        service = {Servlet.class},
        property = {
                "sling.servlet.resourceTypes=" + ElectronicsDSServlet.RESOURCE_TYPE,
                "sling.servlet.methods=GET",
                "sling.servlet.extensions=html"
        }
)
public class ElectronicsDSServlet extends SlingSafeMethodsServlet {
    private static final long serialVersionUID = 1L;

    static final String RESOURCE_TYPE = "aemoperations/components/electronics/alloweddevices";


    @Reference
    protected ExpressionResolver expressionResolver;

    @Override
    protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response)
            throws ServletException, IOException {
        SimpleDataSource allowedDevicesDataSource = new SimpleDataSource(getAllowedDevices(request).iterator());
        request.setAttribute(DataSource.class.getName(), allowedDevicesDataSource);
    }

    protected List<Resource> getAllowedDevices(@NotNull SlingHttpServletRequest request) {
        List<Resource> colors = Collections.emptyList();
        String contentPath = (String) request.getAttribute(Value.CONTENTPATH_ATTRIBUTE);

        if(StringUtils.isEmpty(contentPath)){
            contentPath = request.getParameter("componentPath");
        }

        String dropdownValue = request.getParameter("dropdownValue");
        if(StringUtils.isEmpty(dropdownValue)){
            Config config = getConfig(request);
            ValueMap map = getComponentValueMap(config, request);
            dropdownValue = getParameter(config, "dropdownValue", request);
            if (StringUtils.isEmpty(dropdownValue)) {
                dropdownValue = map != null ? map.get("country", String.class) : StringUtils.EMPTY;
            }
        }
        ResourceResolver resolver = request.getResourceResolver();
        ContentPolicy policy = null;
        if (StringUtils.isNotEmpty(contentPath)) {
            policy = getContentPolicy(contentPath, resolver);
        }
        if (StringUtils.isEmpty(contentPath) || policy == null) {
            contentPath = request.getRequestPathInfo().getSuffix();
            if (StringUtils.isNotEmpty(contentPath)) {
                policy = getContentPolicy(contentPath, resolver);
            }
        }
        if (policy != null) {
            colors = populateDropdown(policy, resolver, dropdownValue);
        }
        return colors;
    }

    private ContentPolicy getContentPolicy(@NotNull String path, @NotNull ResourceResolver resolver) {
        ContentPolicy policy = null;
        ContentPolicyManager policyMgr = resolver.adaptTo(ContentPolicyManager.class);
        Resource contentResource = resolver.getResource(path);
        if (contentResource != null && policyMgr != null) {
            policy = policyMgr.getPolicy(contentResource);
        }
        return policy;
    }

    private List<Resource> populateDropdown(@NotNull ContentPolicy policy, @NotNull ResourceResolver resolver, String dropdownValue) {
        List<Resource> devices = new ArrayList<>();
        ValueMap device = null;
        Resource policyRes = resolver.resolve(policy.getPath());
        Iterator<Resource> children = policyRes.listChildren();
        while (children.hasNext()) {
            final Resource child = children.next();
            if (StringUtils.equalsIgnoreCase(child.getName(), "multifield")) {
                Iterator < Resource > multiChild = child.listChildren();
                while (multiChild.hasNext()) {
                    ValueMap valueMap = multiChild.next().adaptTo(ModifiableValueMap.class);
                    String[] type = valueMap.get("country",String[].class);
                    if(ArrayUtils.contains(type, dropdownValue)){
                        String[] allowedDevices = valueMap.get("devices",String[].class);
                        if (allowedDevices != null && allowedDevices.length > 0) {
                            for (String allowedDevice : allowedDevices) {
                                device = new ValueMapDecorator(new HashMap<>());
                                device.put("text", allowedDevice.toUpperCase());
                                device.put("value", allowedDevice);
                                devices.add(new ValueMapResource(resolver, new ResourceMetadata(), JcrConstants.NT_UNSTRUCTURED,
                                        device));
                            }
                        }
                    }
                }
            }
        }
        return devices;
    }

    Config getConfig(SlingHttpServletRequest request) {
        // get datasource configuration
        Resource datasource = request.getResource().getChild(Config.DATASOURCE);
        if (datasource == null) {
            return null;
        }
        return new Config(datasource);
    }

    protected String getParameter(@NotNull Config config, @NotNull String name,
                                  @NotNull SlingHttpServletRequest request) {
        String value = config.get(name, String.class);
        if (value == null) {
            return null;
        }
        ExpressionHelper expressionHelper = new ExpressionHelper(expressionResolver, request);
        return expressionHelper.getString(value);
    }

    ValueMap getComponentValueMap(Config config, SlingHttpServletRequest request) {
        if (config == null) {
            return null;
        }
        String componentPath = getParameter(config, "componentPath", request);
        if (componentPath == null) {
            return null;
        }

        // get component resource
        Resource component = request.getResourceResolver().getResource(componentPath);
        if (component == null) {
            return null;
        }
        return component.getValueMap();
    }
}

By accessing the component policy and selecting country and electronics, you can dynamically control the available dropdown options. For example, selecting “Country A” displays phone, tablet, and laptop options, while choosing “Country B” shows only phone and tablet options.

Working code for this can be found in the repository: Link to Repository

Design Dialog Configuration
Policy in CRXDE
Working output

Accessing Component Policies in AEM via Path-Based Servlet

How can I leverage component policies chosen at the template level to manage the dropdown-based selection?

AEM has integrated component policies as a pivotal element of the editable template feature. This functionality empowers both authors and developers to provide options for configuring the comprehensive behavior of fully-featured components, including deciding which fields to display, hide, or customize. This configuration is carried out at the template level, facilitating reuse across various templates or template-specific customizations.

Furthermore, AEM introduces a robust styling feature that empowers front-end developers to manage the visual appearance and user interface of components. This grants authors the capability to tailor the look and feel of the component.

Requirement:

In a previous discussion, we outlined a step-by-step process for Accessing Component Policies in AEM at the Component Dialog Level, Sightly code, and Sling model.

Now, consider a scenario where a company is selling various electronic devices in different countries. For example, a company like Brisket sells phones, tablets, and laptops in India, and only phones and tablets in the USA. However, if they plan to introduce laptops in the USA in the future, they’d like to make changes to the template policy. How can this be achieved at the dialog level?

Solution:

Step 1: Create an “Electronics” component and add the design dialog with specific attributes:

Design Dialog

Step 2:  Create the dialog and add an extra granite node with a specific property:

component-path="${requestPathInfo.suffix}" - get component path
Dialog

Step 3: When you open the component dialog and inspect the field, you will see extra attributes, including “component-path.”

Step 4: Create a dialog-level listener that makes a path-based call to a Servlet and passes the attributes as shown.

(function($, $document) {

    $(document).on("foundation-contentloaded", function(e) {
        var value = $("coral-select[name='./country']").val();
        var compPath = $("coral-select[name='./country']").attr("data-component-path");
        populateItems(compPath, value);
    });

    var REGEX_SELECTOR = "dropdown.selector",
        foundationReg = $(window).adaptTo("foundation-registry");

    foundationReg.register("foundation.validation.validator", {
        selector: "[data-foundation-validation='" + REGEX_SELECTOR + "']",
        validate: function(el) {
            if ($(el).is(":visible")) {
                var value = $(el).val();
                var flag = false;
                var compPath = $(el).attr("data-component-path");
                var errorMessage = 'Error occurred during processing';
                populateItems(compPath, value);
            }
        }
    });

    function populateItems(compPath, value) {
        $.ajax({
            type: "GET",
            async: false,
            url: "/bin/electronicsServlet?componentPath=" + compPath + "&dropdownValue=" + value,
            success: function(result) {
                if (result) {
                    var select = document.querySelector("coral-select[name='./devices']");
                    Coral.commons.ready(select, function(component) {
                        component.items.clear();
                        for (var i = 0; i < result.length; ++i) {
                            var option = document.createElement('coral-select-item');
                            option.textContent = result[i].text;
                            option.value = result[i].value;
                            component.items.add(option);
                        }
                    });
                } else {
                    flag = true;
                }
            }
        });
        if (flag) {
            return errorMessage;
        }
    }
}(jQuery, $(document)));

Step 5: Create a registered path-based Servlet that accepts the component path as a parameter, retrieves the component policy, and returns dropdown options.

package com.aem.operations.core.servlets;

import com.adobe.granite.rest.Constants;
import com.day.cq.wcm.api.policies.ContentPolicy;
import com.day.cq.wcm.api.policies.ContentPolicyManager;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.post.JSONResponse;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Component;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

@Component(service = { Servlet.class }, property = {
        "sling.servlet.paths=" + ElectronicsServlet.RESOURCE_PATH, "sling.servlet.methods=GET" })
public class ElectronicsServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;

    static final String RESOURCE_PATH = "/bin/electronicsServlet";

    @Override
    protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response)
            throws ServletException, IOException {
        JsonArray jsonResponse = new JsonArray();
        response.setContentType(JSONResponse.RESPONSE_CONTENT_TYPE);
        response.setCharacterEncoding(Constants.DEFAULT_CHARSET);
        String componentPath = request.getParameter("componentPath");
        String dropdownValue = request.getParameter("dropdownValue");
        if(StringUtils.isNotEmpty(componentPath)){
            ContentPolicy policy = getContentPolicy(componentPath, request.getResourceResolver());
            if(null != policy){
                jsonResponse = populateDropdown(policy, request.getResourceResolver(), dropdownValue);
            }
        }
        try (PrintWriter out = response.getWriter()) {
            out.print(new Gson().toJson(jsonResponse));
        }
    }

    private ContentPolicy getContentPolicy(@NotNull String path, @NotNull ResourceResolver resolver) {
        ContentPolicy policy = null;
        ContentPolicyManager policyMgr = resolver.adaptTo(ContentPolicyManager.class);
        Resource contentResource = resolver.getResource(path);
        if (contentResource != null && policyMgr != null) {
            policy = policyMgr.getPolicy(contentResource);
        }
        return policy;
    }

    private JsonArray populateDropdown(ContentPolicy policy, ResourceResolver resolver, String dropdownValue) {
        JsonArray jsonResponse = new JsonArray();
        Resource policyRes = resolver.resolve(policy.getPath());
        Iterator<Resource> children = policyRes.listChildren();
        while (children.hasNext()) {
            final Resource child = children.next();
            if (StringUtils.equalsIgnoreCase(child.getName(), "multifield")) {
                Iterator < Resource > multiChild = child.listChildren();
                while (multiChild.hasNext()) {
                    ValueMap valueMap = multiChild.next().adaptTo(ModifiableValueMap.class);
                    String[] type = valueMap.get("country",String[].class);
                    if(ArrayUtils.contains(type, dropdownValue)){
                        String[] allowedDevices = valueMap.get("devices",String[].class);
                        if (allowedDevices != null && allowedDevices.length > 0) {
                            for (String allowedDevice : allowedDevices) {
                                JsonObject jsonObj = new JsonObject();
                                jsonObj.addProperty("text", allowedDevice.toUpperCase());
                                jsonObj.addProperty("value", allowedDevice);
                                jsonResponse.add(jsonObj);
                            }
                        }
                    }
                }
            }
        }
        return jsonResponse;
    }
}

By accessing the component policy and selecting country and electronics, you can dynamically control the available dropdown options. For example, selecting “Country A” displays phone, tablet, and laptop options, while choosing “Country B” shows only phone and tablet options.

The working code for this can be found in the repository: Link to Repository

Access template policy
Configure Policy
Saved under policies node as a multifield
Finally dropdown changes

Accessing Component Policies in AEM through the Component Sling Model

Problem Statement:

How can I utilize component policies chosen at the template level to manage the visibility of specific field content within the component’s Sling model code?

Introduction:

AEM has integrated component policies as a pivotal element of the editable template feature. This functionality empowers both authors and developers to provide options for configuring the comprehensive behavior of fully-featured components, including deciding which fields to display, hide, or customize. This configuration is carried out at the template level, facilitating reuse across various templates or template-specific customizations.

Furthermore, AEM introduces a robust styling feature that empowers frontend developers to manage the visual appearance and user interface of components. This grants authors the capability to tailor the look and feel of the component.

Let’s illustrate this with an AEM core component as an example:

Requirement:

In a previous discussion, we outlined a step-by-step process for Accessing Component Policies in AEM at the Component Dialog Level and Sightly code. However, a situation has arisen a question where the field has already been authored on multiple pages, and we now need to avoid displaying this field on pages where it has already been authored. How can this be achieved using the Sling model?

Solution:

This can be achieved at the Sling model level in two ways:

  1. By adapting to Out of the Box (OOTB) “ContentPolicyManager” as shown below:
package com.adobe.cq.wcm.core.components.internal.models.v1;

import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Teaser;
import com.day.cq.wcm.api.designer.Style;
import com.day.cq.wcm.api.policies.ContentPolicy;
import com.day.cq.wcm.api.policies.ContentPolicyManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.*;

@Model(adaptables = SlingHttpServletRequest.class, resourceType = TeaserModel.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME , extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class TeaserModel {
    public final static String RESOURCE_TYPE = "core/wcm/components/teaser/v1/teaser";

    @Self
    private SlingHttpServletRequest request;

    @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
    private String pretitle;

    @SlingObject
    protected Resource resource;

    private boolean pretitleHidden = false;

    public String getPretitle() {
        ContentPolicyManager policyManager = request.getResourceResolver().adaptTo(ContentPolicyManager.class);
        if (policyManager != null) {
            ContentPolicy currentPolicy = policyManager.getPolicy(resource, request);
            if (currentPolicy != null) {
                pretitleHidden = currentPolicy.getProperties().get(Teaser.PN_PRETITLE_HIDDEN, pretitleHidden);
            }
        }
        if(!pretitleHidden){
            return pretitle;
        }
        return StringUtils.EMPTY;
    }
}

2. By Injecting Out of the Box (OOTB) “currentStyle” using “ScriptVariable” as shown below:

package com.adobe.cq.wcm.core.components.internal.models.v1;

import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Teaser;
import com.day.cq.wcm.api.designer.Style;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = SlingHttpServletRequest.class, resourceType = TeaserModel.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME , extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class TeaserModel {
    public final static String RESOURCE_TYPE = "core/wcm/components/teaser/v1/teaser";
    
    @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
    private String pretitle;

    @ScriptVariable
    protected Style currentStyle;

    private boolean pretitleHidden = false;

    public String getPretitle() {
        pretitleHidden = currentStyle.get(Teaser.PN_PRETITLE_HIDDEN, pretitleHidden);
        if(!pretitleHidden){
            return pretitle; 
        }
        return StringUtils.EMPTY;
    }
}

One significant advantage of employing the Sling model for this requirement is the flexibility it offers in implementing and manipulating business logic before delivering results. As demonstrated in the example above, injecting ScriptVariable allows for a more streamlined approach, eliminating the need for extra lines of code such as adaptation, resource resolution, and null checks. The Sling model code dynamically manages the visibility of the “pretitle” field based on the “pretitleHidden” property in the “currentStyle.”

Accessing Component Policies in AEM at the Component Sightly Level

How can I access component policies selected at the template level to control the visibility of specific field content in the component’s Sightly code?

AEM has integrated component policies as a pivotal element of the editable template feature. This functionality empowers both authors and developers to provide options for configuring the comprehensive behavior of fully-featured components, including deciding which fields to display, hide, or customize. This configuration is carried out at the template level, facilitating reuse across various templates or template-specific customizations.

Furthermore, AEM introduces a robust styling feature that empowers frontend developers to manage the visual appearance and user interface of components. This grants authors the capability to tailor the look and feel of the component.

Let’s illustrate this with an AEM core component as an example:

Requirement:

In a previous discussion, we outlined a step-by-step process for Accessing Component Policies in AEM at the Component Dialog Level. However, a situation has arisen a question where the field has already been authored on multiple pages, and we now need to avoid displaying this field on pages where it has already been authored. How can we achieve this?

If the “hidePretitle” checkbox is unchecked at the policy level, the Sightly code should display the content.

Policy unchecked
Sightly Display content


If the “hidePretitle” checkbox is checked at the policy level, the Sightly code should hide the content.

Policy checked
Sightly hide content

Solution:

This can be accomplished at the Sightly level by utilizing the Out of the Box (OOTB) implicit object called “currentStyle,” which implements “com.day.cq.wcm.api.designer.Style” as demonstrated below:

${!currentStyle.pretitleHidden}
<sly data-sly-template.pretitle="${@ teaser}">

    <p class="cmp-teaser__pretitle" data-sly-test.pretitle="${!currentStyle.pretitleHidden}">${teaser.pretitle}</p>

</sly>

This Sightly code dynamically controls the visibility of the “pretitle” based on the “pretitleHidden” property in the “currentStyle.”

Sightly code

Accessing Component Policies in AEM at the Component Dialog Level

How can I access component policies that were chosen at the template level to determine the visibility of specific fields within a component’s dialog?

AEM (Adobe Experience Manager) has integrated component policies as a central feature in the editable template system. This feature empowers both authors and developers to provide options for configuring the comprehensive behavior of fully featured components, including deciding which fields should be shown, hidden, or customized. This configuration can occur at the template level, allowing for reuse across various templates or template-specific customization.

Furthermore, AEM introduces a robust styling feature that empowers frontend developers to manage the visual appearance and user interface of components. This provides authors with the ability to configure the look and feel of components.

Let’s illustrate this with an example using one of AEM’s core components:

Requirement:

We want to prevent authors from editing the Pretitle field in the Teaser component.

Steps to Access the Policy:

Step 1: Navigate to a page in the WKND site via the Site Navigation. In our case, let’s open a magazine page.

WKND site

Step 2: Access the Teaser component, where you will see the Pretitle field enabled.

Teaser Component

Step 3: Now, go to the template editor to access the template structure.

Access Tempalte

Step 4: Click on the Teaser component’s policy.

Access Teaser Policy

Step 5: Select the checkbox to hide the Pretitle field.

Configure Policy

Step 6: Return to the page, refresh it, and open the Teaser component. You’ll notice that you can enable or disable fields using the powerful policy feature.

Teaser component without Pretitle

Now, let’s explore how this works from a backend perspective by accessing CRXDE:

Step 1: In CRXDE, access your project structure to locate your Teaser component.

Path: /apps/wknd/components/teaser
CRXDE: Teaser component

You’ll notice that the component doesn’t have any content, as it overlays the core Teaser component.

Step 2: Navigate to the core Teaser component to access the design dialog (Policy-level fields).

Path: /apps/core/wcm/components/teaser/v2/teaser/cq:design_dialog/content/items/tabs/items/main/items/elements/items/pretitle
CRXDE: Design dialog

You can see that Policy-level fields are located under the design dialog, which can be used to modify the component dialog or behavior. The powerful aspect of policies is that you can have different policies for different templates.

Step 3: Go to the component dialog and examine the Pretitle fields.

Path: /apps/core/wcm/components/teaser/v2/teaser/cq:dialog/content/items/tabs/items/text/items/columns/items/column/items/pretitle
CRXDE: Dialog

You’ll see that a property has been added to the component level using the OOTB (Out of the Box) Granite UI. This can be used for handling:

FilteringResourceWrapper
Render Condition

For more details, you can access these links for Granite UI and documentation.

However, to access any policy field at the component level, you can use the cqDesign implicit variable provided by ExpressionCustomizer by calling the property name as follows:

${cqDesign.[propertyname]} – general field name
For example: ${cqDesign.pretitleHidden} – pretitle field name

AEM Integration with Palantir Foundry

What advantages does the integration of Palantir Foundry with AEM bring, and how can this fusion be achieved effectively?

Palantir Foundry, developed by Palantir Technologies, is a potent data integration and analytics platform. It caters to organizations dealing with vast and intricate datasets, facilitating data-driven decision-making. While it is primarily employed by commercial clients for diverse data tasks such as integration, analysis, and visualization, its scope extends further.

It can also be harnessed for Real-Time Recommendations, providing dynamic and immediate user suggestions. Internally, it employs machine learning models, encompassing collaborative filtering, content-based filtering, and hybrid approaches to create recommendation algorithms within Palantir Foundry.

Benefits of Palantir Foundry Integration with AEM:

Integrating Palantir Foundry with Adobe Experience Manager (AEM) yields several advantages by amalgamating AEM’s web content management prowess with Foundry’s data integration and analytics capabilities. Here are the key benefits of utilizing Palantir Foundry in conjunction with AEM:

  1. Advanced Data Analysis: Palantir Foundry’s robust data analytics tools enable in-depth analysis of user behavior and content performance within AEM. This data-centric approach provides valuable insights to enhance content and user experiences.
  2. Personalization: Integration with Foundry enables the creation of personalized user experiences in AEM based on data insights. Foundry’s analytics can fine-tune content recommendations and user journeys, resulting in heightened engagement and conversion rates.
  3. Enhanced Content Strategy: Foundry’s analytics inform content strategies in AEM by identifying high-performing content and areas requiring improvement. This data-driven approach leads to more effective content creation and distribution.
  4. Scalable Content Personalization: Leveraging Foundry’s analytics, AEM can implement content personalization strategies at scale, serving dynamic and personalized content based on user behavior, preferences, and historical interactions.
  5. Improved User Engagement: AEM can use data from Foundry to create more engaging and relevant content, leading to increased user engagement, longer time spent on the site, and higher user satisfaction.

Data Ingestion:

As depicted in the flow diagram:

  1. Establish a nightly sling scheduler.
  2. Gather all product data, including titles, descriptions, tags, categories, SKUs, and more.
  3. Compile product data into JSON format.
  4. Transmit the data to Palantir Foundry’s S3 bucket for consumption.
Data ingetion flow

Data Processing:

As showcased in the flow diagram:

  1. Retrieve and process the JSON data placed on the S3 bucket.
  2. Employ data models with machine learning algorithms for data analysis.
  3. Fine-tune algorithms based on user engagement metrics.
  4. Prepare personalized data tailored to user IDs (ECIDs).
data processing flow

Real-Time Recommendations:

As outlined in the flow diagram:

  1. A secure API call is made from the AEM front end to Palantir endpoints, utilizing a token system.
  2. Token validation is performed, with Palantir reviewing essential headers, such as ECID.
  3. Palantir Foundry returns recommendation JSON data to AEM.
real-time recommendation flow

Note:

  1. Recommendation models can be further enhanced through A/B testing on AEM web pages.

AEM Component and Template Auditor

How can we effectively assess whether the components and templates within our project structure are actively used on web pages? This knowledge is crucial for making informed decisions regarding component and template maintenance.

When organizations undertake AEM upgrades or migrations, they often introduce new components and templates following revised project structures or archetypes. The entire team usually dedicates itself to developing these components and templates according to Adobe’s recommendations and delivers the project. However, what about the essential cleanup task of removing unused or unreferenced components and templates?


To address this challenge, I have developed a new tool known as the “Component and Template Auditor,” which is available for download or cloning from a Git repository.
After cloning the repository and installing the package into your local environment, you can access the tool.

Within the tool section, navigate to “AEM Operations”

Select the “Component and Template Auditor” tool.

AEM Operations tools


Once the tool is open, you can input the project path and search path as illustrated below:

Auditor tool page


If you have multiple search paths, you can enter them on separate lines, eliminating the need for comma separation.


Click on “Start Auditing” to generate the results, and you can search for component names or count to identify components with zero references (indicating that they are unused), as shown below:

Loading Search results
Zero result table search

This information enables you to make informed decisions about whether to retain or remove the component or template.

Note:

  1. In order to enhance search functionality, the query builder limit has been configured to 5. Consequently, you might observe a maximum of 5 results; however, the actual results might be more than what is displayed.
  2. Please specify search paths as comprehensively as possible to optimize performance.
  3. Run the tool in the most minimal environment available or during off-business hours.

Streamlining AEM Instance Setup and Incremental Builds with Gradle AEM Plugin

Problem Statement:

How to set up an AEM instance in one click?

Can I perform incremental builds on the Maven project?

Introduction:

Gradle AEM Plugin (GAP) developed by Wunderman Thompson Technology uses Gradle as a building tool.

Advantages of Gradle:
1. Flexibility

2. Performance

  • Incrementality — tracks the tasks, watches file changes and runs only what is necessary.
  • Build Cache — Reuses the build outputs of any other Gradle build with the same inputs.
  • Gradle Daemon — A long-lived process that keeps building information “hot” in memory.

3. User experience

How to get started?

Step 1: Create the project outside the Desktop, Documents and Downloads folder to avoid all kinds of setup issues.

Step 2: Create a new project structure using an AEM archetype (using the latest archetype of the below example) you can also run on your current project if it follows the Adobe archetype:

Project Structure

Step 3: Run the following command to integrate AEM archetype with GAP:

curl -OJL https://github.com/Cognifide/gradle-aem-plugin/releases/download/16.0.7/gap.jar && java -jar gap.jar && rm gap.jar

Step 4: Provide AEM Jar and license details and other configurations:

sh gradlew config
  • Provide JAR Path
  • License Path
  • Service Pack path (AEM On-premise, AEM6.5)
  • Core components path
Environment COnfig

Step 5: Run the following command to start the new environment:

sh gradlew :env:setup

Or

sh gradlew
author and publisher env
docker setup running with the author and publisher

Step 6: Run the following command to deploy the code into your local instance:

sh gradlew :all:packageDeploy
package deploy completed
Running instance

Step 7: To stop the environment, you can use the following command:

sh gradlew down

Step 8: To start the environment, you can use the following command:

sh gradlew up

Issues:

  1. If you have multiple project repos, then it’s hard to maintain multiple instances of AEM.
  2. If you have dependency between repos, then incremental build might fail.
  3. If you have installed ACS Commons and if live to reload the bundle or any bundles are in the installed state, then deploy might hang during the bundle stability check.

For more information on GAP plugin please visit the following documentation:

  1. https://github.com/wttech/gradle-aem-plugin
  2. https://github.com/wttech/gradle-aem-plugin/blob/main/docs/launcher.md

Setting Up SonarQube Profiles with AEM Custom Rules: A Guide to Local Development Configuration

Problem Statement:

How to set up SonarQube profiles with AEM custom rules and configure them with local development?

Introduction:

SonarQube is an open-source platform developed by SonarSource for continuous inspection of code quality to perform automatic reviews with static analysis of code to detect bugs and code smells in 29 programming languages. SonarQube offers reports on duplicated code, coding standards, unit tests, code coverage, code complexity, comments, bugs, and security recommendations.

However, it does apply mainly to general Java issues. Adobe Experience Manager is a comprehensive content management platform for building websites, mobile apps and forms. This tool is intended to find common bugs and bad smells specific to AEM development.

Requirements:

Install the docker and keep it up to date

Step 1: Place the following docker-compose YML file under your project structure

Note: Docker image is based out of community image wttech (Cognified team)

version: "latest"

services:
  sonarqube:
    image: ahmedmusallam/sonarqube-aem:latest
    container_name: sonarqube
    depends_on:
      - db
    ports:
      - "9000:9000"
    networks:
      - sonarnet
    environment:
      - sonar.jdbc.username=sonar
      - sonar.jdbc.password=sonar
      - sonar.jdbc.url=jdbc:postgresql://db:5432/sonar
      - SONARQUBE_ADMIN_PASSWORD=Welcome1
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_logs:/opt/sonarqube/logs
  db:
    image: postgres:latest
    container_name: postgres
    networks:
      - sonarnet
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
      - POSTGRES_DB=sonar
    volumes:
      - postgresql_data:/var/lib/postgresql/data

networks:
  sonarnet:
    driver: bridge

volumes:
  sonarqube_data:
  sonarqube_logs:
  postgresql_data:
placing yml file in the project structure

Step 2: Run the below command:

compose up -d

Step 3: After running the command, it will take some time and once the sonar is up you will be able to see the running process in your Docker as shown below:

Docker container


Step 4: Visit the URL: http://localhost:9000/ for the first time and provide the credentials as

Username: admin

Password: admin

Startup page

Step 5: It will ask to change the password please provide the necessary details (Welcome1 preferable)

password update page

Step 6: Visit http://localhost:9000/profiles to check the AEM profiles as shown below:

AEM profiles on SonarQube

Step 7: Now execute the below command to run the sonar analysis on your project:

mvn -Dsonar.login=admin -Dsonar.password=Welcome1 clean install sonar:sonar

Step 8: Visit URL: http://localhost:9000/projects to check your project scan report and overview:

Project scan report

Setting up Sonarqube with Sonarlint:

Requirement:

Install sonarlint plugin into Intelij

Step 1: Click on the Sonarlint plugin configuration as shown below:

SonarLint Config

Step 2: Configure the connection and provide the URL:

Sonar config

Step 3: Generate the token by going into your profile, providing the token name and creating the token or you can also select credentials to authenticate.

Token generation

Step 4: Once you are successfully logged in you will see the success message.

Connection success message

Step 5: Once you analyze the project it will show up the sonar rules based on the Sonaqube profile rules.

connection and project key selection

Invoking Sling Servlet from OSGi service or Sling Model using Sling Servlet Helpers

Problem Statement:

How can I invoke the Sling servlet from the OSGI service or from Sling Model?

Introduction:

We are aware of invoking a service by using @Reference @OSGiService if are referring to any other sling model inside the Sling servlet you can also use adaptTo({class-name}.class) to invoke a sling model within a servlet. But is there any way we can invoke servlet from the Sling model? Or OSGi service?

Yes, we can use the Sling Servlet Helpers bundle provides mock implementations of the SlingHttpServletRequest, SlingHttpServletResponse and related classes, along with fluent SlingInternalRequest and ServletInternalRequest helpers for internal requests.

The mock request/response implementations are meant to be used in tests and also with services like the SlingRequestProcessor when making requests to that service outside of an HTTP request processing context.

They are used under the hood by the SlingInternalRequest and ServletInternalRequest helpers to provide a simple and foolproof way of executing internal Sling requests.

The internal request helpers use either a SlingRequestProcessor to execute internal requests using the full Sling request processing pipeline, or a ServletResolver to resolve and call a Servlet or Script directly. The necessary “mocking” of requests are responses that happen under the hood which leads to much simpler code.

The latter direct-to-servlet (or script) mode is more efficient but less faithful to the way HTTP requests are processed, as it bypasses all Servlet Filters, in particular.

Step 1: Add the following Dependency to your core POM.XML

<dependency>
    <groupId>org.apache.sling</groupId>
    <artifactId>org.apache.sling.servlet-helpers</artifactId>
    <version>1.4.6</version>
</dependency>

The org.apache.sling.servlet-helpers has dependency on the older version of the org.apache.sling.api version. However, you can request AMS to install the bundle manually on Felix console if your Maven build fails

Step 2: Create a simple Servlet as shown below:

package com.chatgpt.core.servlets;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.servlets.post.JSONResponse;
import org.osgi.service.component.annotations.Component;
import com.adobe.granite.rest.Constants;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

@Component(service = { Servlet.class }, property = { "sling.servlet.paths=" + SamplePathServlet.RESOURCE_PATH,
        "sling.servlet.methods=POST" })
public class SamplePathServlet extends SlingAllMethodsServlet {

    private static final long serialVersionUID = 1L;
    public static final String RESOURCE_PATH = "/bin/sampleServletPath";

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType(JSONResponse.RESPONSE_CONTENT_TYPE);
        response.setCharacterEncoding(Constants.DEFAULT_CHARSET);

        JsonObject jsonResponse = new JsonObject();
        jsonResponse.addProperty("mesasge", "I am in Path based Servlet ");
        try (PrintWriter out = response.getWriter()) {
            out.print(new Gson().toJson(jsonResponse));
        }
    }
}

Step 3: Create a Sling model as shown below.

As you can see it internally uses SlingRequestProcessor API to mock internal request

package com.chatgpt.core.models;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.SlingRequestProcessor;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.servlethelpers.internalrequests.SlingInternalRequest;
import org.apache.sling.servlets.post.JSONResponse;
import javax.annotation.PostConstruct;
import java.io.IOException;

@Model(adaptables = SlingHttpServletRequest.class)
public class ExampleModel {
    @OSGiService
    private SlingRequestProcessor slingProcessor;

    @SlingObject
    private ResourceResolver resourceResolver;

    @OSGiService
    private SlingRequestProcessor slingRequestProcessor;

    @PostConstruct
    private void init() {
        try {
            String responString = new SlingInternalRequest(resourceResolver, slingRequestProcessor, "/bin/sampleServletPath")
                    .withRequestMethod("GET")
                    .execute()
                    .checkStatus(200)
                    .checkResponseContentType(JSONResponse.RESPONSE_CONTENT_TYPE+";charset=UTF-8")
                    .getResponseAsString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Step 4: Create an OSGi service as shown below:

package com.chatgpt.core.services.impl;

import java.io.IOException;
import com.chatgpt.core.services.InternalRequestService;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.SlingRequestProcessor;
import org.apache.sling.servlethelpers.internalrequests.SlingInternalRequest;
import org.apache.sling.servlets.post.JSONResponse;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component(service = InternalRequestService.class, immediate = true, name = "Sample Internal Request service")
public class InternalRequestServiceImpl implements InternalRequestService {

    @Reference
    private SlingRequestProcessor slingRequestProcessor;

    @Override
    public String getInternalPathBasedRespone(ResourceResolver resourceResolver) {
        try {
            return new SlingInternalRequest(resourceResolver, slingRequestProcessor, "/bin/sampleServletPath")
                    .withRequestMethod("GET")
                    .execute()
                    .checkStatus(200)
                    .checkResponseContentType(JSONResponse.RESPONSE_CONTENT_TYPE+";charset=UTF-8")
                    .getResponseAsString();
        } catch (IOException e) {
            log.error("An error occurred while proccessing the request {} ", e.getMessage());
        }
        return StringUtils.EMPTY;
    }
}