本文へ移動
cccskills
無料GitHub で公開

replication

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 setup, review checklist, troubleshooting, and common pitfalls.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md37.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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 nameTargetsDefault state
publishLive publish tierAvailable by default in every AEMaaCS environment
previewPreview tierOpt-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:

ConstraintWhy
Use Distributor + SimpleDistributionRequest — not Replicator or ReplicationAgentLegacy APIs are removed from the CS SDK
Resolver via getServiceResourceResolver(SUBSERVICE) — never admin auth or USER/PASSWORD mapsAdmin 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 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 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 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<String, Object> 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 ReplicationActionTypeDistributionRequestType
ACTIVATEADD
DEACTIVATEDELETE
ADDADD
DELETEDELETE
INTERNAL_POLLNo equivalent — drop (CS does not expose poll-style replication)
TESTNo 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<String, Object> 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-<name>.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):

StateMeaning
ACCEPTEDRequest queued for processing
DISTRIBUTEDRequest delivered (returned by some agents/configurations)
DROPPEDRequest rejected — typically a permission, validation, or queue-full issue. Inspect getMessage()
NOT_EXECUTEDDistribution not attempted

Do not poll for DISTRIBUTED. Distribution is asynchronous; the response is your only synchronous signal. For delivery visibility, use the Distribution UI (Tools > Deployment > Distribution) or operational monitoring — see Modern Alternatives and Practical engineering notes.


Greenfield — writing a new distribution caller from scratch

When writing new distribution code (not migrating legacy), use this template. It bundles the AEMaaCS best practices into one self-contained service: run-mode guard, service-user resolver in try-with-resources, agent iteration with defensive preview handling, and JobResult-style outcome classification.

package com.example.distribution;

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.Arrays;
import java.util.Collections;
import java.util.List;

@Component(service = MyDistributionService.class)
public class MyDistributionService {

    private static final Logger LOG = LoggerFactory.getLogger(MyDistributionService.class);
    private static final String SUBSERVICE = "my-distribution-service";

    // Iterate over the agents your site supports. Preview agent absence is handled defensively below.
    private static final List<String> AGENTS = Arrays.asList("publish", "preview");

    @Reference
    private Distributor distributor;

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Reference
    private SlingSettingsService slingSettings;

    public void distribute(String path, DistributionRequestType type) {
        if (!slingSettings.getRunModes().contains("author")) {
            LOG.debug("Skipping distribution on non-author instance for path: {}", path);
            return;
        }

        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SUBSERVICE))) {

            DistributionRequest request = new SimpleDistributionRequest(type, false, path);
            for (String agent : AGENTS) {
                distributeToAgent(agent, resolver, request, path);
            }
        } catch (LoginException e) {
            LOG.error("Could not open service resolver for '{}'", SUBSERVICE, e);
        }
    }

    private void distributeToAgent(String agent, ResourceResolver resolver,
                                   DistributionRequest request, String path) {
        try {
            DistributionResponse response = distributor.distribute(agent, resolver, request);
            if (response.isSuccessful()) {
                LOG.info("[{}] distribution queued for {} (state={})",
                        agent, path, response.getState());
            } else if (isAgentNotConfigured(response)) {
                // Expected when an agent (typically preview) is not enabled for this environment
                LOG.debug("[{}] agent not configured; skipping", agent);
            } else {
                LOG.warn("[{}] distribution failed for {}: state={}, message={}",
                        agent, path, response.getState(), response.getMessage());
            }
        } catch (DistributionException e) {
            LOG.error("[{}] distribution exception for {}", agent, path, e);
        }
    }

    private static boolean isAgentNotConfigured(DistributionResponse response) {
        String message = response.getMessage();
        return message != null && message.contains("no matching agent");
    }
}

What this template captures:

  • Single resolver per call — opened in try-with-resources, reused for all agents in one invocation
  • Per-agent iteration — adding a new agent in future means editing one list, not new code paths
  • Defensive preview handling — opt-in agent absence is logged at debug, not warn
  • Distinct logging for the three outcomes — queued (info), agent-not-configured (debug), real failure (warn)
  • DistributionException caught per agent — one transport error doesn't abort the other agents

Composition — calling Distributor from a JobConsumer

A common production pattern is ResourceChangeListener → JobConsumer → Distributor: the listener stays lightweight (per the resource-change-listener skill), the consumer runs the distribution off the listener thread and gets Sling Job retry semantics for free.

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.event.jobs.Job;
import org.apache.sling.event.jobs.consumer.JobConsumer;
import org.apache.sling.event.jobs.consumer.JobResult;
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 = JobConsumer.class,
    property = { JobConsumer.PROPERTY_TOPICS + "=com/example/replicate" }
)
public class ReplicationJobConsumer implements JobConsumer {

    public static final String JOB_TOPIC = "com/example/replicate";

    private static final Logger LOG = LoggerFactory.getLogger(ReplicationJobConsumer.class);
    private static final String SUBSERVICE = "content-distribution-service";

    @Reference
    private Distributor distributor;

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Override
    public JobResult process(final Job job) {
        final String path = job.getProperty("path", String.class);
        if (path == null) {
            LOG.warn("Job missing 'path' property; CANCEL");
            return JobResult.CANCEL;
        }

        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, SUBSERVICE))) {

            DistributionRequest request = new SimpleDistributionRequest(
                    DistributionRequestType.ADD, false, path);
            DistributionResponse response = distributor.distribute("publish", resolver, request);

            if (response.isSuccessful()) {
                return JobResult.OK;
            }

            // Map DistributionResponse failure modes to JobResult outcomes
            String message = response.getMessage();
            if (message != null
                    && (message.contains("not authorized") || message.contains("no matching agent"))) {
                // Permission or configuration error — retrying won't help. CANCEL drains the queue.
                LOG.error("Unrecoverable distribution failure for {}: {}", path, message);
                return JobResult.CANCEL;
            }

            // Transient (queue full / transport / back-pressure) — FAILED triggers Sling retry per queue config
            LOG.warn("Transient distribution failure for {}: {}", path, message);
            return JobResult.FAILED;

        } catch (LoginException e) {
            LOG.error("Could not open service resolver for '{}'", SUBSERVICE, e);
            return JobResult.FAILED;
        } catch (DistributionException e) {
            LOG.error("Distribution exception for {}", path, e);
            return JobResult.FAILED;
        }
    }
}

Key composition rules:

  • Run-mode guard belongs on the trigger (listener), not the consumer. Sling Jobs may run on any author pod; the listener decides whether to enqueue in the first place.
  • Map DistributionResponse → JobResult deliberately. Permission/agent errors are unrecoverable (CANCEL); transient queue/transport errors are retryable (FAILED).
  • Share the topic constant. The listener and consumer both reference JOB_TOPIC (or a shared constants class) — never copy the literal string into both.
  • Service user must own crx:replicate on the paths the consumer distributes, regardless of which pod runs the job.

Author-only execution and cluster coordination

Distribution is initiated from author. Running it on publish has no effect (publish has no distribution agents pointing outward) and indicates a code-path bug.

Guard at the top of the public method:

@Reference
private SlingSettingsService slingSettings;

public void replicate(String path) {
    if (!slingSettings.getRunModes().contains("author")) {
        return;
    }
    // distribution logic
}

Author-only does not mean single execution. In AEMaaCS author clusters, multiple author pods may receive the same trigger (e.g. a ResourceChangeListener event delivered to each node). If the operation must distribute exactly once per logical change, use a cluster-coordination pattern — leader-instance check (SlingSettingsService topology APIs), distributed lock, or idempotent design at the trigger layer. A run-mode check alone guarantees neither uniqueness nor ordering across the author cluster.


Review Checklist

Cross-cutting:

  • No com.day.cq.replication.* imports remain
  • No org.apache.sling.replication.agent.* imports remain
  • No getAdministrativeResourceResolver() or USER/PASSWORD auth maps remain
  • getServiceResourceResolver(SUBSERVICE) used in try-with-resources
  • OSGi DS R6 annotations — no org.apache.felix.scr.annotations.* imports
  • SLF4J logging — no System.out / System.err / printStackTrace

Distribution call:

  • @Reference Distributor injected — not Replicator or ReplicationAgent
  • SimpleDistributionRequest constructed with explicit DistributionRequestType (ADD / DELETE)
  • deep=true used deliberately — narrow scope when possible
  • Agent name explicit ("publish" and/or "preview") — not a legacy custom agent name
  • If distributing to both tiers, two separate distribute(...) calls
  • DistributionResponse.isSuccessful() checked; getState() and getMessage() logged on failure
  • DistributionException caught and logged

Service user / config:

  • Service user created via Repoinit in ui.config
  • Service user has crx:replicate (not just jcr:read) on distributed paths
  • ServiceUserMapperImpl.amended-*.cfg.json maps <bundle>:<subservice> → the service user
  • Bundle symbolic name in the mapping matches the bundle owning the distribution caller

Execution context:

  • Caller guards with SlingSettingsService.getRunModes().contains("author") (distribution from publish is a no-op)
  • For triggers that may fire on every author cluster node, idempotency is documented or cluster coordination is in place
  • mvn clean install succeeds with no SCR-related or deprecated-API warnings

Troubleshooting

SymptomLog to searchFix direction
LoginException: Unable to retrieve the service resource resolverthe exception name verbatimService user not provisioned via Repoinit, OR ServiceUserMapperImpl.amended-*.cfg.json missing/misnamed, OR bundle symbolic name in the mapping doesn't match the bundle owning the distribution caller
DistributionResponse.getState() == DROPPED with "not authorized" messagestate=DROPPED and not authorized in getMessage()Service user lacks crx:replicate — extend Repoinit ACL to add crx:replicate on the distributed paths
DistributionResponse.getState() == DROPPED with "no matching agent"agent not found or no matching agentFirst argument to distribute(...) is a legacy/custom agent name not present on AEMaaCS — use "publish" or "preview"
Distribution call returns successful but content never appears on publishnone synchronously — in the Distribution UI (Tools > Deployment > Distribution), open the specific agent (publish or preview), then check Queue items for per-item state and Logs for per-item delivery errorsDistribution is asynchronous; the response means queued, not delivered. In the Distribution UI, items move from a persisted state to fully published. Items stuck in persisted indicate the publish tier hasn't drained the queue — open the item to see the failure reason. If the agent's overall status shows blocked or paused, distribution is halted for that agent regardless of individual item state
Same path queued N times in quick successionrepeating Distribution queued for path: … entriesTrigger fires on every author cluster pod — add idempotency check or leader-only guard. The log noise indicates a real coordination bug even if the final delivered outcome looks correct
ClassNotFoundException: com.day.cq.replication.Replicator at runtimethe exception name verbatimLegacy Replicator API is not on the CS SDK classpath — code wasn't fully migrated; complete the P1 + P2 steps
ClassNotFoundException: org.apache.sling.replication.agent.* at runtimethe exception name verbatimSame as above, for the Sling Replication Agent API

Common Pitfalls

Treating isSuccessful() as "delivered" — the response is synchronous; delivery is asynchronous. A successful response means "queued", not "received by publish". Downstream code that assumes immediate availability on publish will see stale or missing content.

Hardcoding a legacy agent name — copying "default", "socialpubsvc", or a customer-specific on-prem agent name into distribute("...", ...). On AEMaaCS the only generally-available agents are "publish" and "preview".

Distributing from publish — code paths that invoke Distributor.distribute(...) from publish will silently no-op or fail (no outbound agents). Always guard with SlingSettingsService.getRunModes().contains("author").

Using getAdministrativeResourceResolver() or USER/PASSWORD auth maps — both are unsupported on CS. The only resolver path is getServiceResourceResolver(SUBSERVICE) with a Repoinit-provisioned service user.

Missing crx:replicate on the service user — without this privilege, Distributor.distribute(...) returns DROPPED. jcr:read alone is not enough.

deep=true on broad paths — new SimpleDistributionRequest(ADD, true, "/content") distributes every descendant. Often unintended and can flood the distribution queue. Use deep=true only when you genuinely need recursive distribution.

Building a custom bulk loop with distribute() — Adobe recommends the Tree Activation workflow step for bulk publishing rather than code that iterates pages calling distribute() per path. The workflow handles batching, throttling, and queue back-pressure. Reserve programmatic distribute() for narrow, targeted activations.

Forgetting to distribute to preview when both tiers are in use — if the site has both publish and preview tiers, distributing only to "publish" leaves preview stale.

Polling for completion — looping on Distributor.distribute(...) calls or sleeping to "wait for replication" is a code smell. Distribution is fire-and-forget from the caller's perspective; use the Distribution UI (Tools > Deployment > Distribution) or operational monitoring for delivery visibility.

Catching Exception and swallowing it — DistributionException carries the actual failure cause (permission, queue config, transport). Catching the broad Exception and logging only the message loses the stack trace and root cause.


Modern Alternatives

NeedUse
Programmatic single-path activation from codeDistributor.distribute("publish", resolver, request) (this skill)
Recursive activation of a content treeSimpleDistributionRequest(ADD, true, path) — used carefully
Multiple paths in one batchSimpleDistributionRequest(ADD, false, path1, path2, ...)
Activation as part of an editorial workflowAEM Workflow + the built-in Activate Page / Deactivate Page workflow step — do not add a parallel Distributor call
Bulk activation of a tree (multiple pages/assets at once)AEM Tree Activation workflow step — Adobe's recommended approach for bulk publishing. Do not build a custom loop calling distribute() per path
Schedule-based distributionSling Distribution Trigger (configured via OSGi factory configs, not code)
Notify external systems when content changesAEM Eventing (not replication) — replication is for moving content between AEM tiers, not for external notification
Initial environment seeding / large-scale content syncAEM Content Transfer Tool (Cloud Manager) — not a code path
Asset distribution to delivery tiersDistribution agent named "publish" handles assets the same way as pages

Expert Guidance

Distribution is asynchronous; design for it. The DistributionResponse tells you whether the request was accepted into the queue, not whether content has reached publish. Treat distribution like sending an email: the API confirms it left your outbox, not that the recipient read it. Use the Distribution UI (Tools > Deployment > Distribution) and the queue state model (persisted → fully published) for delivery visibility — not per-call assertions in code.

Failure modes to plan for:

  • Permission denied — DROPPED state with "not authorized"; fix the service-user ACL (crx:replicate)
  • Agent not found — DROPPED with "no matching agent"; fix the agent name (publish / preview)
  • Queue full / back-pressure — DROPPED or NOT_EXECUTED; transient, retry with backoff
  • DistributionException thrown — the request didn't reach the queue; check the exception cause and resolver state

Do not invent agent names. AEMaaCS provides publish and preview as the predefined agents (publish available by default, preview opt-in per environment). Custom agent names from on-prem setups ("socialpubsvc", "flush", "reverse", customer-specific names) do not exist on CS. If a customer's legacy code references such names, the migration is to map them to publish / preview based on intent, not to recreate the agent.

Preview tier is opt-in. Even teams that "only have publish" today should be aware: if the site introduces preview later, every distribute("publish", ...) call site becomes a place that needs a second distribute("preview", ...) call. Wrapping the distribution in a small helper that takes the agent list as a parameter is cheap insurance.


Practical engineering notes

The following are practical observations from production AEMaaCS code rather than items documented as part of Adobe's official Distribution API contract. They are useful design guidance but should not be treated as guarantees:

  • Author-cluster duplicate calls are usually harmless. When triggers fire on multiple author cluster pods, the underlying distribution agent typically de-duplicates identical pending requests for the same path. Don't rely on this — design idempotency in your trigger logic — but it explains why duplicate distribute() calls in logs often produce a single delivered outcome.
  • Distribution events on author for observability. Sling Distribution publishes OSGi events on topics like org/apache/sling/distribution/agent/package/distributed. Subscribing to these from an EventHandler or JobConsumer is a workable pattern for code that needs to react after a successful distribution, but the topic names and payload are Sling implementation detail, not part of the documented AEMaaCS contract. Verify against the Sling version shipped with your SDK before relying on them.
  • Don't poll the publish tier from author to check whether content arrived. Use the Distribution UI or operational monitoring instead.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Analyzes a multi-step conversion funnel to find where visitors drop off and which steps have the worst leakage. Use this skill when someone describes a journey and asks about conversion rates, drop-off, fallout, or step completion. Trigger for "analyze our checkout funnel," "where are visitors dropping off," "what's our add-to-cart to purchase conversion rate," "funnel analysis," "show me fallout between steps," or "which step loses the most visitors."

日本語の概要は準備中です。原文の説明を表示しています。

aemgdc/aemdev22026年10月10日 更新

Generates a concise, executive-ready performance summary covering key metrics, trends, and what's driving movement. Use this skill when someone needs to produce a briefing, executive summary, performance narrative, or stakeholder readout — for example, "write an exec summary of last week's performance," "create a performance briefing for our leadership team," "produce a monthly business review summary," "what should I tell executives about our metrics," or "generate a performance narrative." Also trigger for "QBR summary," "weekly business review," or "stakeholder briefing."

日本語の概要は準備中です。原文の説明を表示しています。

aemgdc/aemdev22026年10月10日 更新

Produces a compact KPI digest showing how key metrics changed over a period and what's driving the movement. Use this skill when someone asks for a performance summary, a weekly recap, a morning briefing, a KPI update, or any variation of "how did we do this week/month." Also trigger for "give me a performance overview," "what moved in the last 7 days," "pull our AA KPI report," or "summarize our metrics."

日本語の概要は準備中です。原文の説明を表示しています。

aemgdc/aemdev22026年10月10日 更新

Compares the performance of two or more audience segments across key metrics side by side. Use this skill when someone wants to compare audiences or visitor groups — for example, "how do mobile visitors compare to desktop on conversion," "compare new vs. returning visitors," "show me the difference between these two segments," "compare these audiences on our KPIs," or "which segment performs better." Also trigger for "segment comparison" or "audience comparison."

日本語の概要は準備中です。原文の説明を表示しています。

aemgdc/aemdev22026年10月10日 更新

Identifies which items (pages, campaigns, products, channels, regions) had the biggest increases or decreases for a key metric between two time periods. Use this skill when someone asks "what's up and what's down," "which campaigns moved the most," "top gainers and losers," "what pages are trending," "show me what changed by channel," or any variation of identifying the biggest movers and decliners for a metric.

日本語の概要は準備中です。原文の説明を表示しています。

aemgdc/aemdev22026年10月10日 更新

Scan an AEM Edge Delivery Services page for WCAG 2.1 AA accessibility violations and generate specific fixes. Identifies missing alt text, heading hierarchy issues, link text problems, color contrast concerns, and EDS-specific accessibility patterns. Use when fixing accessibility issues, preparing for compliance audits, or remediating WCAG violations.

日本語の概要は準備中です。原文の説明を表示しています。

aemgdc/aemdev22026年10月10日 更新

aemgdc のスキルをすべて見る

このスキルの問題を報告する