Sling-based ResourceChangeListener


Within the robust framework of Adobe Experience Manager (AEM), a variety of APIs exist to capture events, each serving unique purposes. Notable among them are javax.jcr.observation.EventListener, org.osgi.service.event.EventHandler, and org.apache.sling.api.resource.observation.ResourceChangeListener. While these interfaces cater to different event handling scenarios, this blog zeroes in on ResourceChangeListener – the recommended way to navigate and manage resource-level events within the AEM ecosystem.

Understanding ResourceChangeListeners:

At its core, a ResourceChangeListener is a Java interface that observes and reacts to changes within the JCR repository of your AEM instance. Triggered events provide a list of ResourceChange objects, detailing modifications like additions, deletions and modifications

When to use EventListener, EventHandler or ResourceChangeListener:

  1. javax.jcr.observation.EventListener:
    • A low-level API specific to JCR.
    • Captures events related to changes in the content repository.
  2. org.osgi.service.event.EventHandler:
    • Part of the OSGi framework.
    • Listens to events at a higher level within the OSGi framework.
    • More versatile and flexible than JCR-level EventListener.
    • Can be used to observe custom events and events that are not directly related to resources, such as Replication and Workflow events.Can be used to listen to Custom Events and non-resource events like (Replication, Workflow etc).
  3. org.apache.sling.api.resource.observation.ResourceChangeListener:
    • Listens for changes in Sling resources (content nodes in AEM).
    • Preferred for Resource-level event handling, like Resource modified, updated, removed.
    • Does not use long-lived sessions.
    • Resolves Oak observation queue size issues.

Getting Started:

Setting up a basic ResourceChangeListener in AEM involves registering it with OSGi configurations. Follow these steps:

  1. Create a new Java class implementing the ResourceChangeListener interface.
  2. Annotate the class with @Component(service = ResourceChangeListener.class) to register it as an OSGi service.
  3. Implement the onChange method to define actions when a resource change is detected.

Sample Implementation:

/**
* A service to demonstrate how changes in the resource tree
* can be listened for.
* Please note, that apart from EventHandler services,
* the immediate flag should not be set on a service.
*/

@Component(service = ResourceChangeListener.class,
immediate = true,
property = {
ResourceChangeListener.PATHS + "=/content/techrevelaemsite/us/en/products",
//Possible values at https://sling.apache.org/apidocs/sling9/org/apache/sling/api/resource/observation/ResourceChange.ChangeType.html
ResourceChangeListener.CHANGES + "=" + "ADDED",
ResourceChangeListener.CHANGES + "=" + "CHANGED",
ResourceChangeListener.CHANGES + "=" + "REMOVED"
}
)
@ServiceDescription("Demo to listen on changes in the resource tree")
public class SimpleResourceListener implements ResourceChangeListener {

private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void onChange(List<ResourceChange> changes) {
changes.forEach(change -> {
logger.info("Resource event: {} at: {} isExternal", change.getType(), change.getPath(), change.isExternal());
});
}
}

Samples for other use-cases:

Listener for multiple paths:

@Component(service = ResourceChangeListener.class,
immediate = true,
property = {
ResourceChangeListener.PATHS + "=/content/techrevelaemsite/us/en/products",
ResourceChangeListener.PATHS + "=/content/techrevelaemsite/us/en/faqs",

ResourceChangeListener.CHANGES + "=" + "CHANGED",
}
)
public class PropertyChangeListener implements ResourceChangeListener {
private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void onChange(List<ResourceChange> changes) {
changes.forEach(change -> {
logger.info("Resource event: {} at: {}", change.getType(), change.getPath());
});
}
}

Listener with path based on Regex:

A glob pattern must start with the glob: prefix (e.g. glob:**/*.html). The following rules are used to interpret glob patterns:

  • The * character matches zero or more characters of a name component without crossing directory boundaries.
  • The ** characters match zero or more characters crossing directory boundaries.
  • If the pattern is relative (does not start with a slash), the relative path will be appended to all search paths of the resource resolver.

Reference: https://www.javadoc.io/doc/org.apache.sling/org.apache.sling.api/2.16.4/org/apache/sling/api/resource/observation/ResourceChangeListener.html

@Component(service = ResourceChangeListener.class,
immediate = true,
property = {
ResourceChangeListener.PATHS + "=glob:/content/techrevelaemsite/us/**/products/**",
ResourceChangeListener.CHANGES + "=" + "ADDED",
ResourceChangeListener.CHANGES + "=" + "CHANGED",
ResourceChangeListener.CHANGES + "=" + "REMOVED"
}
)
public class SimpleResourceListener implements ResourceChangeListener {
private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void onChange(List<ResourceChange> changes)
changes.forEach(change -> {
logger.info("Resource event: {} at: {}", change.getType(), change.getPath());
});
}
}

Key Capabilities:

  • Targeted Monitoring: Configure listeners to focus on specific paths or resource types for relevant updates.
  • Actionable Insights: Implement custom logic based on the type of change and affected resource.
  • Efficiency and Performance: Efficiently react to changes, optimizing listeners on the JCR level for enhanced AEM application performance. For details on how it optimizes bulk updates, please refer to ResourceChangeListener v/s Sling Event

Practical Applications:

  • Trigger workflows: Automate tasks based on resource changes, like sending notifications.
  • Maintain data consistency: Ensure related resources are updated when a primary resource changes.
  • Enhance user experience: Implement dynamic content updates based on resource modifications.

Implementation Tips:

  • Filter events: Fine-tune listeners to focus on specific changes and avoid unnecessary processing.
  • Performance considerations: Keep onChange methods efficient to maintain optimal AEM performance. For long-running tasks, implement Sling-Jobs. Delegate the processing to Sling Job, after capturing the Event

One thought on “Sling-based ResourceChangeListener

Leave a comment