Expand my Community achievements bar.

Issue with JCR Event Listener in AEMaaCS Migration (Alternative Approach Needed)

Avatar

Level 1

We are migrating our AEM instance from AEM 6.5 to AEM as a Cloud Service (AEMaaCS). In our current implementation, we use a JCR Event Listener that listens to all component changes. Whenever inheritance is canceled for a component, the listener adds the property:

cq:isCancelledForChildren = true

to the parent node.

Problem:

During the migration assessment using BPA (Best Practices Analyzer), we found that JCR Event Listeners are flagged as an issue because they are not recommended in AEMaaCS due to performance concerns.

Question:

What is the best alternative approach in AEMaaCS to achieve the same functionality? Some potential options I’m considering:

  1. ResourceChangeListener – Can it effectively replace the JCR Event Listener for this use case ,but it is also getting flagged in bpa report?
  2. Workflow Launcher – Would this be a viable alternative to automatically add cq:isCancelledForChildren = true when inheritance is canceled?
  3. sling jobs – Could a scheduled job periodically check and update the required properties instead of real-time event listening?

Has anyone implemented a similar functionality in AEMaaCS? Any best practices or recommendations would be appreciated!

Thanks in advance for your insights!

Topics

Topics help categorize Community content and increase your ability to discover relevant content.

1 Reply

Avatar

Level 9

Hi @sai_charanAr ,

 

I see few options how to solve it:
1) Option 1: ResourceChangeListener + SlingJob

Create ResourceChangeListener to catch corresponding events and start sling job with path to node in the event. Sling Job will get parent of your resource and set property. 

Listener:

@Component(service = ResourceChangeListener.class,
        immediate = true,
        property = {
                ResourceChangeListener.PATHS + "= /content/my-site",
                ResourceChangeListener.CHANGES + "=ADDED",
                ResourceChangeListener.CHANGES + "=CHANGED"
        })
public class ResourceChangeJobListener implements ResourceChangeListener {

    private static final String JOB_TOPIC = "com/example/job/setProperty";

    @Reference
    private JobManager jobManager;

    @Override
    public void onChange(List<ResourceChange> changes) {
        for (ResourceChange change : changes) {
            String resourcePath = change.getPath();
            jobManager.addJob(JOB_TOPIC, Collections.singletonMap("resourcePath", resourcePath));
        }
    }
}

Sling Job:

@Component(service = JobConsumer.class,
        immediate = true,
        property = {
                JobConsumer.PROPERTY_TOPICS + "=com/example/job/setProperty"
        })
public class SetPropertyJobConsumer implements JobConsumer {

    @Reference
    private ResourceResolverFactory resolverFactory;

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

        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "data-writer"))) {

            Resource resource = resolver.getResource(resourcePath);
            if (resource != null) {
                Resource parentResource = resource.getParent();
                if (parentResource != null) {
                    ModifiableValueMap properties = parentResource.adaptTo(ModifiableValueMap.class);
                    if (properties != null) {
                        properties.put("cq:isCancelledForChildren", true);
                        resolver.commit();
                    }
                }
            }
        } catch (PersistenceException e) {
            return JobResult.FAILED;
        }

        return JobResult.OK;
    }
}

2) Option 2: Workflow Launcher 

You can implement simple workflow process and create workflow launcher that will be executed only by configured conditions. 

Workflow process: 

@Component(service = WorkflowProcess.class,
        property = {"process.label=Set cq:isCancelledForChildren on Parent"})
public class CancelChildrenWorkflowProcess implements WorkflowProcess {

    private static final Logger LOG = LoggerFactory.getLogger(CancelChildrenWorkflowProcess.class);

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Override
    public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaDataMap) throws WorkflowException {
        String payloadPath = workItem.getWorkflowData().getPayload().toString();

        if (StringUtils.isBlank(payloadPath)) {
            LOG.warn("Workflow payload is empty or invalid");
            return;
        }

        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "workflow-service"))) {

            Resource resource = resolver.getResource(payloadPath);
            if (resource != null) {
                Resource parentResource = resource.getParent();
                if (parentResource != null) {
                    ModifiableValueMap properties = parentResource.adaptTo(ModifiableValueMap.class);
                    if (properties != null) {
                        properties.put("cq:isCancelledForChildren", true);
                        resolver.commit();
                        LOG.info("Successfully set cq:isCancelledForChildren=true on {}", parentResource.getPath());
                    } else {
                        LOG.warn("Could not adapt parent resource to ModifiableValueMap at {}", parentResource.getPath());
                    }
                } else {
                    LOG.warn("Parent resource not found for {}", payloadPath);
                }
            } else {
                LOG.warn("Resource not found at {}", payloadPath);
            }

        } catch (PersistenceException | LoginException e) {
            LOG.error("Error updating cq:isCancelledForChildren on parent node", e);
            throw new WorkflowException("Failed to update cq:isCancelledForChildren", e);
        }
    }
}

 

Best regards,

Kostiantyn Diachenko.