Install
$ agentstack add skill-adobe-skills-replication ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Replication / Content Distribution — AEM as a Cloud Service
Overview
On AEM as a Cloud Service, replication is performed via the Sling Distribution API (org.apache.sling.distribution.Distributor). The legacy CQ Replicator (com.day.cq.replication.*) and Sling Replication Agent (org.apache.sling.replication.agent.*) APIs are not supported — code using them either compiles against legacy AEM 6.x jars or fails at runtime on CS.
AEMaaCS provides two predefined replication agents:
| Agent name | Targets | Default state | |-----------|---------|---------------| | publish | Live publish tier | Available by default in every AEMaaCS environment | | preview | Preview tier | Opt-in — only available when the preview tier is enabled for the environment |
publish is the default agent for activation; preview must be explicitly enabled. If both tiers are in use, call distributor.distribute(...) twice — once per agent name. Legacy Replicator.replicate(...) implicitly fanned out to every configured agent; Distributor.distribute(...) is explicit and targets one named agent per call.
Three CS-specific constraints every distribution call must satisfy:
| Constraint | Why | |-----------|-----| | Use Distributor + SimpleDistributionRequest — not Replicator or ReplicationAgent | Legacy APIs are removed from the CS SDK | | Resolver via getServiceResourceResolver(SUBSERVICE) — never admin auth or USER/PASSWORD maps | Admin resolvers are unavailable on CS; service-user auth is the only supported path | | Inspect DistributionResponse.isSuccessful() and getState() | Distributor.distribute() returns a queued/accepted response — it does NOT block for delivery |
> Distributor.distribute() is asynchronous. A successful response means the distribution request was queued, not delivered. Do not assume content has reached the publish tier just because the call returned isSuccessful() == true. See [Expert Guidance](#expert-guidance) below.
Classification — choose before making any changes
Identify the source pattern in the file:
Uses com.day.cq.replication.Replicator with ReplicationAction and ReplicationActionType (ACTIVATE, DEACTIVATE) → Apply P1–P4.
Uses org.apache.sling.replication.agent.api.ReplicationAgent with ReplicationResult and agent.replicate(resolver, type, path) → Apply P1–P4.
Uses Distributor + SimpleDistributionRequest already → Already on the target API — verify against the [Review Checklist](#review-checklist) only.
Uses WorkflowSession.startWorkflow(...) with a replication launcher → This skill covers programmatic distribution. If replication is tied to a content workflow step, the workflow handles it — leave the workflow alone, do not introduce a parallel Distributor call.
One pattern per session. If the bundle has multiple legacy classes, migrate one class at a time.
Before starting: Read [../references/aem-cloud-service-pattern-prerequisites.md](../references/aem-cloud-service-pattern-prerequisites.md) and apply SCR→DS, service-user, and SLF4J fixes if present in the same changeset.
Complete example — before and after
Before (legacy CQ Replicator with admin resolver)
package com.example.replication;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import com.day.cq.replication.ReplicationAction;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.Replicator;
import java.util.HashMap;
import java.util.Map;
@Component(immediate = true)
@Service
public class ContentReplicationService {
@Reference
private Replicator replicator;
@Reference
private ResourceResolverFactory resolverFactory;
public void replicateContent(String contentPath) {
ResourceResolver resolver = null;
try {
Map authInfo = new HashMap<>();
authInfo.put(ResourceResolverFactory.USER, "replication-service");
authInfo.put(ResourceResolverFactory.PASSWORD, "password");
resolver = resolverFactory.getAdministrativeResourceResolver(authInfo);
ReplicationAction action = new ReplicationAction(ReplicationActionType.ACTIVATE, contentPath);
replicator.replicate(resolver, action);
System.out.println("Replicated: " + contentPath);
} catch (Exception e) {
System.err.println("Replication failed: " + e.getMessage());
e.printStackTrace();
} finally {
if (resolver != null && resolver.isLive()) {
resolver.close();
}
}
}
}
After — Cloud Service compatible
package com.example.replication;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.distribution.DistributionException;
import org.apache.sling.distribution.DistributionRequest;
import org.apache.sling.distribution.DistributionRequestType;
import org.apache.sling.distribution.DistributionResponse;
import org.apache.sling.distribution.Distributor;
import org.apache.sling.distribution.SimpleDistributionRequest;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
@Component(service = ContentReplicationService.class)
public class ContentReplicationService {
private static final Logger LOG = LoggerFactory.getLogger(ContentReplicationService.class);
private static final String SUBSERVICE = "content-distribution-service";
@Reference
private Distributor distributor;
@Reference
private ResourceResolverFactory resolverFactory;
@Reference
private SlingSettingsService slingSettings;
public void replicateContent(String contentPath) {
if (!slingSettings.getRunModes().contains("author")) {
LOG.debug("Skipping distribution on non-author instance for path: {}", contentPath);
return;
}
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SUBSERVICE))) {
DistributionRequest request = new SimpleDistributionRequest(
DistributionRequestType.ADD, false, contentPath);
DistributionResponse response = distributor.distribute("publish", resolver, request);
if (response.isSuccessful()) {
LOG.info("Content distribution queued for path: {} (state={})",
contentPath, response.getState());
} else {
LOG.warn("Content distribution not queued for path: {} (state={}, message={})",
contentPath, response.getState(), response.getMessage());
}
} catch (LoginException e) {
LOG.error("Could not open service resolver for subservice '{}'", SUBSERVICE, e);
} catch (DistributionException e) {
LOG.error("Distribution failed for path: {}", contentPath, e);
}
}
}
Required Repoinit (goes in your ui.config Repoinit OSGi config):
create service user content-distribution-service
set ACL for content-distribution-service
allow jcr:read,crx:replicate on /content
end
The crx:replicate privilege is required for the service user to initiate distribution — read access alone is not sufficient.
Required service-user mapping (ui.config, file named e.g. org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-content-distribution.cfg.json):
{
"user.mapping": [
"com.example.mybundle:content-distribution-service=[content-distribution-service]"
]
}
P1 — Replace Replicator / ReplicationAgent with Distributor
For com.day.cq.replication.Replicator:
// BEFORE
@Reference
private Replicator replicator;
ReplicationAction action = new ReplicationAction(ReplicationActionType.ACTIVATE, path);
replicator.replicate(resolver, action);
// AFTER
@Reference
private Distributor distributor;
DistributionRequest request = new SimpleDistributionRequest(
DistributionRequestType.ADD, false, path);
DistributionResponse response = distributor.distribute("publish", resolver, request);
For org.apache.sling.replication.agent.api.ReplicationAgent:
// BEFORE
@Reference
private ReplicationAgent agent;
ReplicationResult result = agent.replicate(resolver, ReplicationActionType.ADD, path);
if (result.isSuccessful()) { /* ... */ }
// AFTER
@Reference
private Distributor distributor;
DistributionRequest request = new SimpleDistributionRequest(
DistributionRequestType.ADD, false, path);
DistributionResponse response = distributor.distribute("publish", resolver, request);
if (response.isSuccessful()) { /* ... */ }
ReplicationActionType → DistributionRequestType mapping:
| Legacy ReplicationActionType | DistributionRequestType | |--------------------------------|---------------------------| | ACTIVATE | ADD | | DEACTIVATE | DELETE | | ADD | ADD | | DELETE | DELETE | | INTERNAL_POLL | No equivalent — drop (CS does not expose poll-style replication) | | TEST | No equivalent — drop |
SimpleDistributionRequest constructor:
new SimpleDistributionRequest(
DistributionRequestType type, // ADD or DELETE
boolean deep, // include descendants (recursive distribution)
String... paths // one or more paths
)
deep = truedistributes the path and all descendants. Use sparingly — a singledeep=trueon/content/mysitecan queue thousands of nodes.- Pass multiple paths for a single batched request:
new SimpleDistributionRequest(ADD, false, path1, path2, path3).
Distributing to both publish and preview tiers:
DistributionResponse publishResponse = distributor.distribute("publish", resolver, request);
DistributionResponse previewResponse = distributor.distribute("preview", resolver, request);
Two separate calls — there is no "distribute to all" agent name.
Handle the "preview not enabled" case gracefully. Because preview is opt-in per environment, code that distributes to both tiers must tolerate the absence of the preview agent. The DistributionResponse for an unconfigured agent comes back as DROPPED with a "no matching agent" message — this is not an error condition in environments that haven't enabled preview:
DistributionResponse previewResponse = distributor.distribute("preview", resolver, request);
if (previewResponse.isSuccessful()) {
LOG.info("Preview distribution queued for path: {}", path);
} else if (previewResponse.getMessage() != null
&& previewResponse.getMessage().contains("no matching agent")) {
// Preview tier not enabled for this environment — expected on publish-only setups
LOG.debug("Preview agent not configured; skipping preview distribution for {}", path);
} else {
LOG.warn("Preview distribution failed for {}: state={}, message={}",
path, previewResponse.getState(), previewResponse.getMessage());
}
Treating "no matching agent" as a warning rather than an info-level log in a publish-only environment creates alert noise; treating it as an error breaks deployments. Inspect the message and log at debug.
P2 — Update imports
Remove (CQ Replicator):
import com.day.cq.replication.ReplicationAction;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.Replicator;
import com.day.cq.replication.ReplicationException;
Remove (Sling Replication Agent):
import org.apache.sling.replication.agent.api.ReplicationAgent;
import org.apache.sling.replication.agent.api.ReplicationAgentConfiguration;
import org.apache.sling.replication.agent.api.ReplicationAgentException;
import org.apache.sling.replication.agent.api.ReplicationResult;
import org.apache.sling.replication.agent.api.ReplicationActionType;
import org.apache.sling.replication.agent.impl.SimpleReplicationAgent;
Remove (SCR → DS):
import org.apache.felix.scr.annotations.*;
Add:
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.distribution.DistributionException;
import org.apache.sling.distribution.DistributionRequest;
import org.apache.sling.distribution.DistributionRequestType;
import org.apache.sling.distribution.DistributionResponse;
import org.apache.sling.distribution.Distributor;
import org.apache.sling.distribution.SimpleDistributionRequest;
import org.apache.sling.settings.SlingSettingsService; // only if guarding by run mode
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
P3 — Service-user resolver and auth
Remove all legacy auth patterns:
// REMOVE — admin auth is unavailable on CS
resolver = resolverFactory.getAdministrativeResourceResolver(null);
// REMOVE — USER/PASSWORD auth maps do not work on CS
Map authInfo = new HashMap<>();
authInfo.put(ResourceResolverFactory.USER, "...");
authInfo.put(ResourceResolverFactory.PASSWORD, "...");
resolver = resolverFactory.getResourceResolver(authInfo);
Replace with a service-user resolver in try-with-resources:
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "content-distribution-service"))) {
// distribution call
} catch (LoginException e) {
LOG.error("Could not open service resolver", e);
}
getServiceResourceResolver throws LoginException on failure — it does not normally return null. Catch LoginException; do not add a resolver == null branch unless a custom wrapper is in use.
Service user privileges: the user needs crx:replicate (in addition to jcr:read) on every path it distributes. Without it, Distributor.distribute(...) returns a non-successful response with a "not authorized" message. Permissions must be declared directly on the service user — do not rely on permissions inherited via group membership.
Mapping file conventions:
- Use the amend form (
ServiceUserMapperImpl.amended-.cfg.json) — do not edit the baseServiceUserMapperImplconfig. - Use principal-name mapping: the square-bracket form
=[service-user-name](as shown above) is the principal-name form. - Do not use the deprecated
userNameform (single user without brackets) — it is being phased out across Sling.
P4 — Inspect DistributionResponse
Distributor.distribute(...) returns a DistributionResponse describing the outcome of queuing, not the outcome of delivery. Always inspect it:
DistributionResponse response = distributor.distribute("publish", resolver, request);
if (response.isSuccessful()) {
LOG.info("Distribution queued: state={}", response.getState());
} else {
// common non-successful states: DROPPED, NOT_EXECUTED
LOG.warn("Distribution not queued: state={}, message={}",
response.getState(), response.getMessage());
}
DistributionResponse.getState() values (most common):
| State | Meaning | |-------|---------| | ACCEPTED | Request queued f
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: adobe
- Source: adobe/skills
- License: Apache-2.0
- Homepage: https://www.adobe.com/ai/overview.html
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.