AEM Cloud Service - Embargo Pages and Publish to Preview Only Restrictions


Add a feature in AEM to support Embargo Content. Pages marked for embargo should not be allowed intentional or accidental activate to publish env, however Controlled Activation to Preview should be allowed for specific users...



Set crx:replicate Deny at Root



Publish Actions Unavailable 




Publish to Preview via Workflow




1) Add a Restriction Pattern core\src\main\java\apps\experienceaem\sites\core\acls\EAEMEmbargoTypeRestriction.java

package apps.experienceaem.sites.core.acls;

import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;

public class EAEMEmbargoTypeRestriction implements RestrictionPattern {
private final String restrictedValue;
public static final String RESTRICTION_TYPE_EMBARGO = "embargoedContent";

EAEMEmbargoTypeRestriction(String restrictedValue) {
this.restrictedValue = restrictedValue;
}

public boolean matches(Tree tree, PropertyState propertyState) {
Tree child = tree.getChild(JcrConstants.JCR_CONTENT);

if(!child.hasProperty(RESTRICTION_TYPE_EMBARGO)){
return false;
}

String value = child.getProperty(RESTRICTION_TYPE_EMBARGO).getValue(Type.STRING);

return restrictedValue.equalsIgnoreCase(value);
}

public boolean matches(String path) {
return false;
}

public boolean matches() {
return false;
}
}

2) Add the Restriction Provider core\src\main\java\apps\experienceaem\sites\core\acls\EAEMPublishRestrictionProvider.java

package apps.experienceaem.sites.core.acls;

import com.google.common.collect.ImmutableMap;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.*;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component(
service = RestrictionProvider.class
)
public class EAEMPublishRestrictionProvider extends AbstractRestrictionProvider {
private static final Logger log = LoggerFactory.getLogger(EAEMPublishRestrictionProvider.class);

public EAEMPublishRestrictionProvider() {
super(supportedRestrictions());
}

private static Map<String, RestrictionDefinition> supportedRestrictions() {
RestrictionDefinition embargoRes = new RestrictionDefinitionImpl(EAEMEmbargoTypeRestriction.RESTRICTION_TYPE_EMBARGO,
Type.STRING, false);
return ImmutableMap.of(embargoRes.getName(), embargoRes);
}

@Override
public RestrictionPattern getPattern(String oakPath, Tree tree) {
if (oakPath == null) {
return RestrictionPattern.EMPTY;
} else {
List<RestrictionPattern> patterns = new ArrayList(1);

PropertyState embargoProperty = tree.getProperty(EAEMEmbargoTypeRestriction.RESTRICTION_TYPE_EMBARGO);

if (embargoProperty != null) {
patterns.add(new EAEMEmbargoTypeRestriction(embargoProperty.getValue(Type.STRING)));
}

return CompositePattern.create(patterns);
}
}

@Override
public RestrictionPattern getPattern(String oakPath, Set<Restriction> restrictions) {
if (oakPath == null || restrictions.isEmpty()) {
return RestrictionPattern.EMPTY;
} else {
List<RestrictionPattern> patterns = new ArrayList(2);

for (Restriction r : restrictions) {
String name = r.getDefinition().getName();

if (EAEMEmbargoTypeRestriction.RESTRICTION_TYPE_EMBARGO.equals(name)) {
patterns.add(new EAEMEmbargoTypeRestriction(r.getProperty().getValue(Type.STRING)));
} else {
log.debug("EAEMPublishRestrictionProvider : Ignoring unsupported restriction " + name);
}
}

return CompositePattern.create(patterns);
}
}
}

3) Workflow process step for Publishing the Page to Preview only using service user eaem-embargo-service eaem-page-replicate-restriction\core\src\main\java\apps\experienceaem\sites\core\acls\EmbargoReplicationToPreview.java

package apps.experienceaem.sites.core.acls;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import com.day.cq.replication.*;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Session;
import java.util.HashMap;
import java.util.Map;

@Component(
service = WorkflowProcess.class,
property = { "process.label=Experience AEM Embargo Replicate to Preview Process Step" })
public class EmbargoReplicationToPreview implements WorkflowProcess {
private static final Logger log = LoggerFactory.getLogger(EmbargoReplicationToPreview.class.getName());

private static final String PREVIEW_AGENT = "preview";

@Reference
ResourceResolverFactory resourceResolverFactory;

@Reference
Replicator replicator;

public void execute(final WorkItem workItem, final WorkflowSession workflowSession, final MetaDataMap args)
throws WorkflowException {
String pagePath = getPayloadPath(workItem.getWorkflowData());

try{
ResourceResolver serviceResolver = getEmbargoServiceResolver();

ReplicationOptions options = new ReplicationOptions();

options.setFilter(new AgentFilter() {
@Override
public boolean isIncluded(Agent agent) {
return agent.getId().equals(PREVIEW_AGENT);
}
});

replicator.replicate(serviceResolver.adaptTo(Session.class), ReplicationActionType.ACTIVATE, pagePath, options);

ReplicationStatus repStatus = serviceResolver.getResource(pagePath).adaptTo(ReplicationStatus.class);

ReplicationStatus previewStatus = repStatus.getStatusForAgent(PREVIEW_AGENT);

log.info("Page Replication status of {} is {} done by {} ", pagePath,
previewStatus.getLastReplicationAction().getName(), previewStatus.getLastPublishedBy());
}catch(Exception e){
log.error("Error while publish to preview {} ", pagePath, e);
}
}

private String getPayloadPath(WorkflowData wfData) {
String payloadPath = null;

if (wfData.getPayloadType().equals("JCR_PATH")) {
payloadPath = (String)wfData.getPayload();
}

return payloadPath;
}

private ResourceResolver getEmbargoServiceResolver() throws Exception{
Map<String, Object> SERVICE_MAP = new HashMap<>();
SERVICE_MAP.put(ResourceResolverFactory.SUBSERVICE, "eaem-embargo-service");

return resourceResolverFactory.getServiceResourceResolver(SERVICE_MAP);
}
}

4) Create the service user eaem-embargo-service and give necessary replicate permissions in ui.config\src\main\content\jcr_root\apps\eaem-page-replicate-restriction\osgiconfig\config\org.apache.sling.jcr.repoinit.RepositoryInitializer~eaem.cfg.json

{
"scripts": [
"create path (sling:OrderedFolder) /content/dam/eaem-page-replicate-restriction",
"create path (nt:unstructured) /content/dam/eaem-page-replicate-restriction/jcr:content",
"set properties on /content/dam/eaem-page-replicate-restriction/jcr:content\n set cq:conf{String} to /conf/eaem-page-replicate-restriction\n set jcr:title{String} to \"AEM Page Replicate Restriction Provider\"\nend",
"create service user eaem-embargo-service with path system/experience-aem",
"set ACL for eaem-embargo-service \n allow crx:replicate on /content \n allow jcr:read, rep:write on / \n end"
]
}

5) Service user to bundle mapping in ui.config\src\main\content\jcr_root\apps\eaem-page-replicate-restriction\osgiconfig\config\org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-eaem.cfg.json

{
"user.mapping": [
"eaem-page-replicate-restriction.core:eaem-embargo-service=[eaem-embargo-service]"
]
}

6) Workflow model config in ui.content\src\main\content\jcr_root\conf\global\settings\workflow\models\embargo-publish-to-preview\.content.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
jcr:primaryType="cq:Page">
<jcr:content
cq:designPath="/libs/settings/wcm/designs/default"
cq:lastModified="{Date}2025-07-30T17:25:27.518-05:00"
cq:lastModifiedBy="admin"
cq:template="/libs/cq/workflow/templates/model"
jcr:primaryType="cq:PageContent"
jcr:title="Embargo Service Publish To Preview"
sling:resourceType="cq/workflow/components/pages/model"
lastSynced="{Date}2025-07-30T17:25:32.864-05:00">
<flow
jcr:primaryType="nt:unstructured"
sling:resourceType="foundation/components/parsys">
<process
jcr:created="{Date}2025-07-30T17:25:04.730-05:00"
jcr:createdBy="admin"
jcr:description="Publish to Preview using Service User"
jcr:lastModified="{Date}2025-07-30T17:25:27.514-05:00"
jcr:lastModifiedBy="admin"
jcr:primaryType="nt:unstructured"
jcr:title="Publish to Preview using Service User"
sling:resourceType="cq/workflow/components/model/process">
<metaData
jcr:primaryType="nt:unstructured"
PROCESS="apps.experienceaem.sites.core.acls.EmbargoReplicationToPreview"
PROCESS_AUTO_ADVANCE="true"/>
</process>
</flow>
</jcr:content>
</jcr:root>

7) Page dialog extension to add tab for Embargo properties in ui.apps\src\main\content\jcr_root\apps\eaem-page-replicate-restriction\components\page\_cq_dialog\.content.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured">
<content jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<tabs jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<eaem
cq:showOnCreate="{Boolean}true"
jcr:primaryType="nt:unstructured"
jcr:title="Experience AEM"
sling:orderBefore="socialmedia"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<section
jcr:primaryType="nt:unstructured"
jcr:title="Experience AEM"
sling:resourceType="granite/ui/components/coral/foundation/form/fieldset">
<items jcr:primaryType="nt:unstructured">
<embargo
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
checked="{Boolean}false"
fieldDescription="If checked, page is embargoed and only available in preview"
name="./embargoedContent"
text="Is Embargoed Page?"
value="true"/>
</items>
</section>
</items>
</column>
</items>
</eaem>
</items>
</tabs>
</items>
</content>
</jcr:root>

No comments:

Post a Comment