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

Best Practices for Writing a Sling Servlet

Problem Statement:

What is the best way to write a Sling servlet?

Requirement:

The article aims to address the problem of finding the best way to write a Sling servlet and the recommended way to register it in Apache Sling.

Introduction:

Servlets and scripts are themselves resources in Sling and thus have a resource path: this is either the location in the resource repository, the resource type in a servlet component configuration or the “virtual” bundle resource path (if a script is provided inside a bundle without being installed into the JCR repository).

OSGi DS – SlingServletResourceTypes

OSGi DS 1.4 (R7) component property type annotations for Sling Servlets (recommended)

  • Component as servlet based on OSGi R7/8
  • Provide resourcetype, methods, and extensions
  • ServiceDescription for the servlet
package com.mysite.core.servlets;

import com.day.cq.commons.jcr.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceDescription;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

@Component(service = { Servlet.class })
@SlingServletResourceTypes(resourceTypes = "mysite/components/page", methods = HttpConstants.METHOD_GET, extensions = "txt")
@ServiceDescription("Simple Demo Servlet")
public class SimpleServlet extends SlingSafeMethodsServlet {

	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
			throws ServletException, IOException {
		final Resource resource = req.getResource();
		resp.setContentType("text/plain");
		resp.getWriter().write("Title = " + resource.getValueMap().get(JcrConstants.JCR_TITLE));
	}
}

Simple OSGi DS 1.2 annotations

Registering Servlet by path

  • Component as servlet based on OSGi R7/8
  • Servlet paths, method, and other details
package com.mysite.core.servlets;

import com.drew.lang.annotations.NotNull;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;

@Component(service = { Servlet.class }, configurationPolicy = ConfigurationPolicy.REQUIRE, property = {
        "sling.servlet.paths=" + PathBased.RESOURCE_PATH, "sling.servlet.methods=GET" })
public class PathBased extends SlingSafeMethodsServlet {
    static final String RESOURCE_PATH = "/bin/resourcepath";

    @Override
    protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response)
            throws ServletException, IOException {
    }
}
@SlingServlet(
    resourceTypes = "/apps/my/type",
    selectors = "hello",
    extensions = "html",
    methods = "GET")
public class MyServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
        ...
    }
}

Configuration Policy – Required

  • configuration policy is mandatory to avoid calling any servlet or services outside a particular environment
  • Its always recommended to run certain services like data sources to work only on authors
<strong>File Name - com.mysite.core.servlets.PathBased</strong>
<?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:OsgiConfig"/>

Project Structure

References:

https://sling.apache.org/documentation/the-sling-engine/servlets.html