Loading Now

AEM: Sling Framework

aem-sling-framework

AEM: Sling Framework

Understanding Sling Resource Resolution, Sling Models, Exporters, and Sling Jobs

The Apache Sling framework forms the core of request handling in Adobe Experience Manager (AEM). It introduces a powerful, resourceโ€‘centric approach for resolving requests, mapping URL structures, building models, exporting content as JSON, and processing asynchronous tasks through jobs.
This article breaks down four major Sling concepts:

  1. Sling Resource Resolution Framework
  2. Sling Models & Adaptation
  3. Sling Exporter Framework
  4. Sling Jobs & Job Processing

Sling Resource Resolution Framework

In Sling, everything is a resource. When a request comes in, Sling evaluates the URL and maps it to the best possible matching resource inside the repository. To do this, Sling decomposes the URL into distinct logical parts.

URL Decomposition

Consider the URL: /a/b.s1.s2.html/c/d.s.txt?q=x

This decomposes into:

PartValueDescription
Resource Path/a/bThe longest existing path inside the repository
Selectorss1.s2Parts after the first dot but before the final dot before the extension
ExtensionhtmlThe part after the last dot before the next slash
Suffix/c/d.s.txtEverything after extension until end of the URL
Query Parameterq=xStandard query string

Rules of Decomposition

1. Resource Path

  • Longest path inside the repository.
  • Must exist; otherwise Sling falls back to resource resolution rules.

2. Selectors

Selectors are optional decorations added to request processing.
Rules:

  • They exist only if the first character after the resource path is a dot.
  • The segment between this first dot and the last dot before the extension is the selectors string.
  • If there is only one dot before the extension, no selectors exist.

Example:
/content/page.print.a4.html
Selectors = print, a4
Extension = html

Selectors are often used for:

  • Rendering variants (mobile/desktop)
  • Export modes (print, data)
  • Data views (summary/full)

3. Extension

  • Follows the last dot before a slash or end of URL.
  • Defines the response type (e.g., html, json, xml).

4. Suffix

  • Begins with / after selectors and extension.
  • Often used to pass a path-like value to servlets/components.

Example for URL Decomposition & Request Handling (Resource Path, Selectors, Extension, Suffix)

Servlet that reads selectors, extension, and suffix

@Component(service = Servlet.class, property = {
    	ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=stacknowledge/components/page",
    	ServletResolverConstants.SLING_SERVLET_SELECTORS + "=s1",
    	ServletResolverConstants.SLING_SERVLET_EXTENSIONS + "=html",
    	ServletResolverConstants.SLING_SERVLET_METHODS + "=GET"
})
public class UrlDecompositionServlet extends SlingSafeMethodsServlet {

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

    	// Resource path (resolved resource)
    	Resource resource = request.getResource();
    	String resourcePath = resource.getPath();

    	// Selectors (array)
    	String[] selectors = request.getRequestPathInfo().getSelectors();

    	// Extension
    	String extension = request.getRequestPathInfo().getExtension();

    	// Suffix (as string and resource)
    	String suffix = request.getRequestPathInfo().getSuffix();
    	Resource suffixResource = request.getRequestPathInfo().getSuffixResource();

    	response.setContentType("text/plain");
    	response.getWriter().printf(
        	"Resource path: %s%nSelectors: %s%nextension: %s%nsuffix: %s%nsuffixResource: %s%n",
        	resourcePath,
        	Arrays.toString(selectors),
        	extension,
        	suffix,
        	suffixResource != null ? suffixResource.getPath() : "null"
    	);
	}
}

How it resolves for: /a/b.s1.s2.html/c/d.s.txt?q=x

  • getResource().getPath() โ†’ /a/b
  • getRequestPathInfo().getSelectors() โ†’ [“s1″,”s2”]
  • getRequestPathInfo().getExtension() โ†’ html
  • getRequestPathInfo().getSuffix() โ†’ /c/d.s.txt

Sling Models

Sling Models offer a clean, annotationโ€‘driven way to map Sling resources or requests to Java POJOs.

Adapter Concept

Sling Models rely on the Sling Adapter Framework. Adapting means: โ€œTake one Sling object, convert it into another representation.โ€

Common adaptables:

  • Resource.class
  • SlingHttpServletRequest.class

Adapting from Resource

@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ProductCardModel {

	@ValueMapValue @Default(values = "My Title")
	private String title;

	@ValueMapValue @Default(values = "/content/dam/stacknowledge/pic.jpg")
	private String image;

	@ValueMapValue @Default(booleanValues = false)
	private boolean featured;

	@ValueMapValue @Default(intValues = 3)
	private int maxItems;

	public String getTitle() { return title; }
	public String getImage() { return image; }
	public boolean isFeatured() { return featured; }
	public int getMaxItems() { return maxItems; }
}
//Usage
ProductCardModel model = resource.adaptTo(ProductCardModel.class);
  • Resource โ†’ Model adaption internally uses ValueMap.
  • Values injected into fields come from CRX properties.
  • Good for simple data-backed components.

Adapting from SlingHttpServletRequest

@Model(adaptables = SlingHttpServletRequest.class)
public class ProductTile {
	@Inject @Self private SlingHttpServletRequest request;
	@Inject @Reference private HybrisConnectionManager connectionManager;

	@Inject @Via("resource")
	@Default(intValues = 3)
	private int featuresShown;
}

Why adapt from request?

  • Gives access to request parameters, selectors, suffix, etc.
  • Useful when business logic depends on runtime conditions.

@Self, @Via, @Default, and @Reference simplify dependency retrieval.

Alternative: Model Factory

@Reference
private ModelFactory modelFactory;
...
MyModel model = modelFactory.createModel(object, MyModel.class);

Used when:

  • The adaptable object is dynamic.
  • You want more control over model creation.

Sling Exporter

Sling Exporter allows converting Sling Models into structured JSON output (usually for SPA frameworks such as React/Vue).

@Model(adaptables = SlingHttpServletRequest.class,
   	adapters = { MyComponentModel.class, ContainerExporter.class },
   	resourceType = {"/apps/stacknowledge/myComponent", "/apps/stacknowledge/myComponent2"},
   	defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json")
public class MyComponentModel implements ContainerExporter {
...
}

Key Concepts

  • @Exporter(name=”jackson”) โ†’ Uses Jackson for JSON generation.
  • extensions = “json” โ†’ Access via .model.json.
  • Exporters automatically serialize all getters.

ComponentExporter vs ContainerExporter

InterfaceResponsibilities
ComponentExporterProvides component type via getExportedType()
ContainerExporterExtends ComponentExporter, adds:โ€ข getExportedItems()โ€ข getExportedItemsOrder()

Override these to customize JSON output for SPA Components.


Sling Jobs

Sling Jobs provide asynchronous processing capabilities, ideal for tasks such as:

  • Data synchronization
  • Email sending
  • Re-indexing
  • Heavy computation

Job Structure

Every job has:

  1. Job Topic โ€“ identifies job type.
  2. Payload โ€“ keyโ€“value map of serializable data.

Creating a Job

Traditional approach

jobManager.addJob("stacknowledge/jobs/topic", props);

Using JobBuilder (recommended)

jobManager.createJob("stacknowledge/jobs/topic")
      	.properties(props)
      	.add();

add() must be the final method โ†’ it queues the job.

@Component(service = JobStarter.class)
public class JobStarter {

	public static final String JOB_TOPIC = "stacknowledge/jobs/topic";

	@Reference
	private JobManager jobManager;

	public void start(String path, String user) {
    	Map<String, Object> props = new HashMap<>();
    	props.put("path", path);
    	props.put("initiatedBy", user);
    	props.put("priority", "normal");

    	// Recommended: JobBuilder
    	JobBuilder builder = jobManager.createJob(JOB_TOPIC).properties(props);
    	builder.add(); // enqueue
	}
}

JobConsumer

A job consumer listens for specific topics.

@Component(service = JobConsumer.class, property = {
    	JobConsumer.PROPERTY_TOPICS + "=" + JobStarter.JOB_TOPIC
})
public class MyJobConsumer implements JobConsumer {

	@Override
	public JobResult process(Job job) {
    	String path = (String) job.getProperty("path");
    	String user = (String) job.getProperty("initiatedBy");

    	try {
        	// Execute business logic
        	// ...
        	return JobResult.OK;
    	} catch (CustomException e) {
        	// Retryable failure
        	return JobResult.FAILED;
    	} catch (Exception e) {
        	// Permanent failure
        	return JobResult.CANCEL;
    	}
	}

	private static class CustomException extends RuntimeException {}
}

Possible Results

  • JobResult.OK โ†’ Success
  • JobResult.FAILED โ†’ Retry allowed
  • JobResult.CANCEL โ†’ Permanent failure

JobExecutor

@Component(service = JobExecutor.class,
       	property = { JobExecutor.PROPERTY_TOPICS + "=" + JobStarter.JOB_TOPIC})
public class MyJobExecutor implements JobExecutor {

	@Override
	public boolean process(final Job job) {
    	String path = (String) job.getProperty("path");
    	job.getProgressLog().log("Starting for: " + path);

    	try {
        	// Step 1
        	job.getProgressLog().log("Fetching remote data...");
        	// ...

        	// Step 2
        	job.getProgressLog().log("Applying transformations...");
        	// ...

        	// Step 3
        	job.getProgressLog().log("Persisting results...");
        	// ...

        	job.getProgressLog().log("Completed.");
        	return true; // success
    	} catch (Exception e) {
        	job.getProgressLog().log("Error occurred: " + e.getMessage());
        	return false; // triggers retry based on configuration
    	}
	}
}

Use JobExecutor when:

  • You need progress trackers.
  • You want to attach more detailed metadata.
  • You require advanced error reporting.

Minimal pom.xml dependencies

Version numbers depend on your AEM/Sling baseline. Use the AEM Project Archetype to bootstrap a correct set.

<dependencies>
  <!-- Sling Models -->
  <dependency>
	<groupId>org.apache.sling</groupId>
	<artifactId>org.apache.sling.models.api</artifactId>
	<version>1.5.0</version>
	<scope>provided</scope>
  </dependency>

  <!-- Sling API -->
  <dependency>
	<groupId>org.apache.sling</groupId>
	<artifactId>org.apache.sling.api</artifactId>
	<version>2.25.0</version>
	<scope>provided</scope>
  </dependency>

  <!-- OSGi Annotations -->
  <dependency>
	<groupId>org.osgi</groupId>
	<artifactId>org.osgi.service.component.annotations</artifactId>
	<version>1.4.0</version>
	<scope>provided</scope>
  </dependency>

  <!-- Sling Event / Jobs -->
  <dependency>
	<groupId>org.apache.sling</groupId>
	<artifactId>org.apache.sling.event</artifactId>
	<version>4.3.8</version>
	<scope>provided</scope>
  </dependency>

  <!-- AEM SPA Exporter APIs (if using ContainerExporter) -->
  <dependency>
	<groupId>com.adobe.cq</groupId>
	<artifactId>core.wcm.components.core</artifactId>
	<version>2.21.2</version>
	<scope>provided</scope>
  </dependency>
</dependencies>