AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Replication

skill-adobe-skills-replication · by adobe

AEM Cloud Service expert skill for replication / content distribution. Covers migration from CQ Replicator (com.day.cq.replication.Replicator) and Sling Replication Agent (org.apache.sling.replication.agent.api) to the Sling Distribution API (Distributor + SimpleDistributionRequest). Includes agent selection (publish vs preview), async response handling, author cluster coordination, service-user…

No reviews yet
0 installs
25 views
0.0% view→install

Install

$ agentstack add skill-adobe-skills-replication

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-adobe-skills-replication)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Replication? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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()) { /* ... */ }

ReplicationActionTypeDistributionRequestType 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 = true distributes the path and all descendants. Use sparingly — a single deep=true on /content/mysite can 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 base ServiceUserMapperImpl config.
  • Use principal-name mapping: the square-bracket form =[service-user-name] (as shown above) is the principal-name form.
  • Do not use the deprecated userName form (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.

Versions

  • v0.1.0 Imported from the upstream source.