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

Best Practices for Creating Extensible and Secure Sling Models in AEM

Problem Statement:

What is the best way to use the Sling model?

Requirements:

Need to create a sling model with best practices like OOTB components and increase the extensibility of the component and security.

Introduction:

The Sling model is a basic POJO, the class is annotated with @Model and the adaptable class. Fields that need to be injected are annotated with recommended injectors.

Its commonly used for injecting component resources and handling manipulation on those properties

Create a Sling model interface

Create a Sling model interface class and impl package with its implementation this helps in the long run when we are trying to write extra features by creating versions for the same component:

Create Interface Class – HelloWorldModel

In the below code, we can see an interface that extends the Component and a few constants and methods which increases the security

package com.mysite.core.models;

import com.adobe.cq.wcm.core.components.models.Component;
import org.osgi.annotation.versioning.ConsumerType;

@ConsumerType
public interface HelloWorldModel extends Component {
    /**
     * if required by multiple classes
     */
    String PN_PARENT_PAGE = "navigationRoot";

    /**
     * Returns the hellow world message
     *
     * @return the message containing resourcetype and page path.
     */
    default String getMessage() {
        throw new UnsupportedOperationException();
    }
}
Create Implementation Class – HelloWorldModelImpl
package com.mysite.core.models.impl;

import static org.apache.sling.api.resource.ResourceResolver.PROPERTY_RESOURCE_TYPE;
import java.util.Optional;
import javax.annotation.PostConstruct;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Default;
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.SlingObject;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.mysite.core.models.HelloWorldModel;

@Model(adaptables = SlingHttpServletRequest.class, adapters = {HelloWorldModel.class}, resourceType = HelloWorldModelImpl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME , extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class HelloWorldModelImpl implements HelloWorldModel {

    public static final String RESOURCE_TYPE = "mysite/components/content/hello";

    @ValueMapValue(name=PROPERTY_RESOURCE_TYPE, injectionStrategy=InjectionStrategy.OPTIONAL)
    @Default(values="No resourceType")
    protected String resourceType;

    @SlingObject
    private Resource currentResource;

    @SlingObject
    private ResourceResolver resourceResolver;

    private String message;

    @PostConstruct
    protected void init() {
        PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
        String currentPagePath = Optional.ofNullable(pageManager)
                .map(pm -> pm.getContainingPage(currentResource))
                .map(Page::getPath).orElse("");

        message = "Hello World!\n"
                + "Resource type is: " + resourceType + "\n"
                + "Current page is:  " + currentPagePath + "\n";
    }

    public String getMessage() {
        return message;
    }
}

Implementation class will implement and adapters to interface class HellWorldModel

Please make sure

  1. The default adaption(adaptables) strategy is preferably SlingHTTPRequest compared or Resource
  2. We should always add Jackson exporter so that the model will be export ready
  3. Make sure always use relevant injector instead of @inject annotations refer to all the injectors: https://sling.apache.org/documentation/bundles/models.html
  4. if multiple fields have the optional injection, then add the following code to the class to make all the variables optional
@Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

Project structure

AEM Core Image component:

References

Interface:
https://github.com/adobe/aem-core-wcm-components/blob/master/bundles/core/src/main/java/com/adobe/cq/wcm/core/components/models/Image.java

Implementation:
https://github.com/adobe/aem-core-wcm-components/blob/master/bundles/core/src/main/java/com/adobe/cq/wcm/core/components/internal/models/v3/ImageImpl.java

Advantages of this pattern:

  1. Create multiple versions of the component
  2. Improves code readability
  3. Don’t have to retouch the existing implementation but can extend the parent class to improve functionality
  4. Easy to revert back to the production code
  5. Improves unit testing and regression testing