Hi community,
I am managing a site that has a multi language / country following the msm structure.
When I need to delete a page in the language master folder, is there a way that I can apply the delete action to the sibling live copy pages under the other country / language folder?
Site Structure
Thanks,
Chris
Solved! Go to Solution.
Topics help categorize Community content and increase your ability to discover relevant content.
Views
Replies
Total Likes
implement an "event-listener" that listens for page deletions in the source language-master folder, finds live copies, and deletes them automatically.
Create a Sling event listener that listens to JCR NODE_REMOVED events under /content/language-master.
package com.example.aem.listeners;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.Session;
import org.apache.sling.api.resource.PersistenceException;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.RepositoryException;
import javax.jcr.Node;
import com.day.cq.wcm.msm.api.LiveRelationshipManager;
import com.day.cq.wcm.msm.api.LiveRelationship;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Component(
immediate = true,
service = EventListener.class,
property = {
"event.topics=org/apache/jackrabbit/jcr/observation/namespace/REMOVED",
"event.types=NODE_REMOVED"
}
)
public class PageDeleteListener implements EventListener {
private static final Logger LOG = LoggerFactory.getLogger(PageDeleteListener.class);
@reference
private ResourceResolverFactory resolverFactory;
@reference
private LiveRelationshipManager liveRelationshipManager;
private static final String LANGUAGE_MASTER_PATH = "/content/language-master";
@Override
public void onEvent(EventIterator events) {
while (events.hasNext()) {
Event event = events.nextEvent();
try {
String path = event.getPath();
// Check if deleted node is under language-master
if (path != null && path.startsWith(LANGUAGE_MASTER_PATH)) {
LOG.info("Detected deletion of source page: {}", path);
// Obtain a ResourceResolver with write permission
Map<String, Object> params = new HashMap<>();
params.put(ResourceResolverFactory.SUBSERVICE, "datawrite"); // make sure this service user exists with required perms
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(params)) {
Resource deletedPageResource = resolver.getResource(path);
// Since node is deleted, this resource might be null
// Instead, get the parent path to find live copies
String sourcePagePath = path;
// Get live relationships for the source path
Collection<LiveRelationship> liveRelationships = liveRelationshipManager.getLiveRelationships(sourcePagePath);
for (LiveRelationship liveRel : liveRelationships) {
Resource liveCopy = liveRel.getLiveCopy();
if (liveCopy != null) {
LOG.info("Deleting live copy page at: {}", liveCopy.getPath());
deletePage(resolver, liveCopy);
}
}
resolver.commit();
} catch (Exception e) {
LOG.error("Failed to delete live copies for source page: {}", path, e);
}
}
} catch (RepositoryException e) {
LOG.error("Error processing deletion event", e);
}
}
}
private void deletePage(ResourceResolver resolver, Resource pageResource) throws PersistenceException {
if (pageResource != null) {
resolver.delete(pageResource);
LOG.info("Deleted page {}", pageResource.getPath());
}
}
}
- Configure a service user (e.g., datawrite) with write permissions on your content paths (including language-master and live copies).
- Bind the service user in your OSGi config for ResourceResolverFactory under Apache Sling Service User Mapper Service.
- Make sure the service user has jcr:removeChildNodes and jcr:removeNode permissions on the live copy paths.
- Deploy your listener bundle.
- Test by deleting a page under /content/language-master and verify if the corresponding live copies get deleted.
Hi @ChrisCh4,
In AEM's MSM (Multi Site Manager), deleting a page in the language master does not automatically delete the corresponding live copies in the country/language sites. This is by design to avoid accidental data loss across localized sites that may have customizations.
However, there are a few options to propagate deletion manually or semi-automatically:
Before deletion, deactivate/unpublish the master page and its live copies:
Go to the language-master page (e.g., /language-master/english/target-page-to-be-deleted
).
Use Manage Publication to deactivate the page and select all live copies to include.
Once deactivated, delete the page in the language master.
Then, manually delete each corresponding live copy in the country/language folders.
Select the language master page in Sites.
Open the References panel.
Under "Live Copies", you’ll see all live copies for that page.
You can click each one and delete them manually.
If you frequently need to delete master pages and their live copies:
Write a custom Groovy script or AEM Workflow using JCR APIs:
Identify the page in the language master.
Traverse live copy references using the cq:LiveRelationship
or blueprint info.
Delete each live copy programmatically.
This method requires AEM developer access and proper testing in non-prod first.
If you need to proceed with the automated approach, it's not too complicated. You can create a workflow that performs the following actions:
- Find live copies of target page
- First deactivate and delete them
- Finally, deactivate and delete the target page
Here is a code sample to retrieve live copy pages:
import com.day.cq.wcm.msm.api.LiveRelationshipManager;
import com.day.cq.wcm.msm.api.LiveRelationship;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
@Reference
private LiveRelationshipManager liveRelationshipManager;
public List<Resource> getAllLiveCopies(ResourceResolver resourceResolver, String sourcePath) {
List<Resource> liveCopies = new ArrayList<>();
try {
Resource sourceResource = resourceResolver.getResource(sourcePath);
if (sourceResource != null) {
// Get all live relationships where this page is the source
Iterator<LiveRelationship> relationships =
liveRelationshipManager.getLiveRelationships(sourceResource, null, null);
while (relationships.hasNext()) {
LiveRelationship relationship = relationships.next();
Resource liveCopyResource = relationship.getTargetResource();
if (liveCopyResource != null) {
liveCopies.add(liveCopyResource);
}
}
}
} catch (Exception e) {
// Handle exception
log.error("Error retrieving live copies for path: " + sourcePath, e);
}
return liveCopies;
}
Code to deactivate pages:
import com.day.cq.replication.Replicator;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.ReplicationException;
import org.apache.sling.api.resource.ResourceResolver;
import javax.jcr.Session;
@Reference
private Replicator replicator;
public boolean deactivatePage(ResourceResolver resourceResolver, String pagePath) {
try {
Session session = resourceResolver.adaptTo(Session.class);
// Deactivate the page
replicator.replicate(session, ReplicationActionType.DEACTIVATE, pagePath);
return true;
} catch (ReplicationException e) {
log.error("Error deactivating page: " + pagePath, e);
return false;
}
}
implement an "event-listener" that listens for page deletions in the source language-master folder, finds live copies, and deletes them automatically.
Create a Sling event listener that listens to JCR NODE_REMOVED events under /content/language-master.
package com.example.aem.listeners;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.Session;
import org.apache.sling.api.resource.PersistenceException;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.RepositoryException;
import javax.jcr.Node;
import com.day.cq.wcm.msm.api.LiveRelationshipManager;
import com.day.cq.wcm.msm.api.LiveRelationship;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Component(
immediate = true,
service = EventListener.class,
property = {
"event.topics=org/apache/jackrabbit/jcr/observation/namespace/REMOVED",
"event.types=NODE_REMOVED"
}
)
public class PageDeleteListener implements EventListener {
private static final Logger LOG = LoggerFactory.getLogger(PageDeleteListener.class);
@reference
private ResourceResolverFactory resolverFactory;
@reference
private LiveRelationshipManager liveRelationshipManager;
private static final String LANGUAGE_MASTER_PATH = "/content/language-master";
@Override
public void onEvent(EventIterator events) {
while (events.hasNext()) {
Event event = events.nextEvent();
try {
String path = event.getPath();
// Check if deleted node is under language-master
if (path != null && path.startsWith(LANGUAGE_MASTER_PATH)) {
LOG.info("Detected deletion of source page: {}", path);
// Obtain a ResourceResolver with write permission
Map<String, Object> params = new HashMap<>();
params.put(ResourceResolverFactory.SUBSERVICE, "datawrite"); // make sure this service user exists with required perms
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(params)) {
Resource deletedPageResource = resolver.getResource(path);
// Since node is deleted, this resource might be null
// Instead, get the parent path to find live copies
String sourcePagePath = path;
// Get live relationships for the source path
Collection<LiveRelationship> liveRelationships = liveRelationshipManager.getLiveRelationships(sourcePagePath);
for (LiveRelationship liveRel : liveRelationships) {
Resource liveCopy = liveRel.getLiveCopy();
if (liveCopy != null) {
LOG.info("Deleting live copy page at: {}", liveCopy.getPath());
deletePage(resolver, liveCopy);
}
}
resolver.commit();
} catch (Exception e) {
LOG.error("Failed to delete live copies for source page: {}", path, e);
}
}
} catch (RepositoryException e) {
LOG.error("Error processing deletion event", e);
}
}
}
private void deletePage(ResourceResolver resolver, Resource pageResource) throws PersistenceException {
if (pageResource != null) {
resolver.delete(pageResource);
LOG.info("Deleted page {}", pageResource.getPath());
}
}
}
- Configure a service user (e.g., datawrite) with write permissions on your content paths (including language-master and live copies).
- Bind the service user in your OSGi config for ResourceResolverFactory under Apache Sling Service User Mapper Service.
- Make sure the service user has jcr:removeChildNodes and jcr:removeNode permissions on the live copy paths.
- Deploy your listener bundle.
- Test by deleting a page under /content/language-master and verify if the corresponding live copies get deleted.
Views
Likes
Replies
Views
Likes
Replies
Views
Likes
Replies