Table of Contents
Tags
Share
Our site pages are stored under /content.
All the Experience fragments are stored under /content/experience-fragments. When we were designing the structure for experience fragment (XF) pages we wanted them to correlate to our existing site pages.
Unlike ordinary AEM pages, XF pages cannot be created one under another. In order to mimic the structure of our main site, or just to group fragments logically in a tree structure, we can create folders/subfolders.

/site-com - blueprint
/site-com-live - live copies
de_de
en_us
it_it
fr_frSo, the structure of a single XF will be like this:
/content
/experience-fragments
/site-com
/xf-test
xf-test - main variation - "blueprint" version
en_us
it_it
de_de
fr_fr/apps/msm/site-com/blueprintconfigs/xf-blueprint:
<!--?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="cq:Page">
<jcr:content cq:template="/libs/wcm/msm/templates/blueprint" jcr:primarytype="nt:unstructured" jcr:title="XF Blueprint" sling:resourcetype="wcm/msm/components/blueprint" sitepath="/content/experience-fragments/site-com" thumbnailrotate="0">
<dialog></dialog>
</jcr:content>
</jcr:root>Rewriting XF links
When we rollout a page containing an XF e.g. to fr_fr, and there is french version of that XF, we would expect to automatically see the french XF variation on the french site page. To achieve this we need to create a custom rollout action.
@Component(immediate = true,
service = LiveActionFactory.class,
property = {
LiveActionFactory.LIVE_ACTION_NAME + "=" + XFReferencesUpdateActionFactory.LIVE_ACTION_CLASS_NAME,
LiveActionFactory.LIVE_ACTION_NAME + "=" + XFReferencesUpdateActionFactory.LIVE_ACTION_NAME
})
public class XFReferencesUpdateActionFactory extends FilteredActionFactoryBase<xfreferencesupdateactionfactory.xfreferencesupdateaction> {
public static final String LIVE_ACTION_CLASS_NAME = "XFReferencesUpdateAction";
public static final String LIVE_ACTION_NAME = "referencesUpdateXF";
@Reference
private RolloutManager rolloutManager;
@Reference
private LiveRelationshipManager relationshipManager;
@Activate
@Modified
protected void configure(ComponentContext context) {
setupFilter(context, this.rolloutManager);
}
@Override
protected XFReferencesUpdateAction newActionInstance(ValueMap valueMap) throws WCMException {
return new XFReferencesUpdateAction(valueMap, this.getPagePropertyFilter(), this.getComponentFilter(), this);
}
@Override
public String createsAction() {
return LIVE_ACTION_NAME;
}
class XFReferencesUpdateAction extends FilteredAction {
protected XFReferencesUpdateAction(ValueMap configuration, ItemFilterImpl pageItemFilter, ItemFilterImpl componentItemFilter, BaseActionFactory<!--? extends FilteredAction--> factory) {
super(configuration, pageItemFilter, componentItemFilter, factory);
}
@Override
protected boolean doHandle(Resource source, Resource target, LiveRelationship relation, boolean resetRollout)
throws RepositoryException, WCMException {
return (resourceHasNode(source)) && (resourceHasNode(target) &&
(source.isResourceType(ExperienceFragmentsConstants.RT_EXPERIENCE_FRAGMENT_COMPONENT)));
}
@Override
protected void doExecute(Resource source, Resource target, LiveRelationship relation, boolean resetRollout)
throws RepositoryException, WCMException {
String fragmentPath = source.getValueMap().get(ExperienceFragmentsConstants.PN_FRAGMENT_PATH, StringUtils.EMPTY);
ResourceResolver resolver = target.getResourceResolver();
PageManager pageManager = resolver.adaptTo(PageManager.class);
Resource fragment = resolver.getResource(fragmentPath);
Page targetPage = pageManager.getPage(relation.getLiveCopy().getPath());
Page targetLanguageRoot = targetPage.getAbsoluteParent(2);
String targetLanguage = targetLanguageRoot.getName();
adjustReferences(pageManager, fragment, target, targetLanguage);
}
private void adjustReferences(PageManager pageManager, Resource fragment, Resource target, String targetLanguage) throws RepositoryException, WCMException {
RangeIterator relationshipsIterator = relationshipManager.getLiveRelationships(fragment, null, null);
while (relationshipsIterator.hasNext()) {
LiveRelationship relationship = (LiveRelationship) relationshipsIterator.next();
Page fragmentLiveCopyPage = pageManager.getPage(relationship.getLiveCopy().getPath());
if (isSubjectForReferencesAdjustment(fragmentLiveCopyPage, targetLanguage)) {
new ReferenceSearch().adjustReferences(target.adaptTo(Node.class), relationship
.getSourcePath(), relationship
.getTargetPath(), true, Collections.emptySet());
}
}
}
private boolean isSubjectForReferencesAdjustment(Page fragmentLiveCopyPage, String targetLanguage) {
return fragmentLiveCopyPage != null && fragmentLiveCopyPage.getPath().endsWith(targetLanguage);
}
}
}
</xfreferencesupdateactionfactory.xfreferencesupdateaction>/apps/msm/wcm/rolloutconfigs/site-com/updateXFReferences
<!--?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="cq:Page">
<jcr:content cq:defaultview="html" cq:rolloutconfigid53="cq:trigger=rollout#updateContent/status=true####" cq:template="/libs/wcm/msm/templates/rolloutconfig" cq:trigger="rollout" jcr:description="Updating experience fragment references for regional sites on rollout trigger" jcr:primarytype="nt:unstructured" jcr:title="Update XF references config" sling:resourcetype="wcm/msm/components/rolloutconfig">
<referencesupdatexf jcr:primarytype="cq:LiveSyncAction"></referencesupdatexf>
</jcr:content>
</jcr:root>
Rewriting site links inside XFs
When we roll out an XF page we expect all the site links to be rewritten according to the locale we are rolling it out to. We will create another rollout action to take care of the site link inside an XF.
@Component(immediate = true,
service = LiveActionFactory.class,
property = {
LiveActionFactory.LIVE_ACTION_NAME + "=" + SiteReferencesUpdateActionFactory.LIVE_ACTION_CLASS_NAME,
LiveActionFactory.LIVE_ACTION_NAME + "=" + SiteReferencesUpdateActionFactory.LIVE_ACTION_NAME
})
public class SiteReferencesUpdateActionFactory extends FilteredActionFactoryBase<sitereferencesupdateactionfactory.sitereferencesupdateaction> {
public static final String LIVE_ACTION_CLASS_NAME = "SiteReferencesUpdateAction";
public static final String LIVE_ACTION_NAME = "referencesUpdateSite";
private static final String CONTENT_PATH_REGEXP = "/content/(site-com)[w-/]*";
private static final Pattern CONTENT_PATH_PATTERN = Pattern.compile(CONTENT_PATH_REGEXP);
@Reference
private RolloutManager rolloutManager;
@Reference
private LiveRelationshipManager relationshipManager;
@Activate
@Modified
protected void configure(ComponentContext context) {
setupFilter(context, this.rolloutManager);
}
@Override
protected SiteReferencesUpdateAction newActionInstance(ValueMap valueMap) throws WCMException {
return new SiteReferencesUpdateAction(valueMap, this.getPagePropertyFilter(), this.getComponentFilter(), this);
}
@Override
public String createsAction() {
return LIVE_ACTION_NAME;
}
class SiteReferencesUpdateAction extends FilteredAction {
protected SiteReferencesUpdateAction(ValueMap configuration, ItemFilterImpl pageItemFilter, ItemFilterImpl componentItemFilter, BaseActionFactory<!--? extends FilteredAction--> factory) {
super(configuration, pageItemFilter, componentItemFilter, factory);
}
@Override
protected boolean doHandle(Resource source, Resource target, LiveRelationship relation, boolean resetRollout)
throws RepositoryException, WCMException {
return resourceHasNode(source) && resourceHasNode(target);
}
@Override
protected void doExecute(Resource source, Resource target, LiveRelationship relation, boolean resetRollout)
throws RepositoryException, WCMException {
ResourceResolver resolver = target.getResourceResolver();
PageManager pageManager = resolver.adaptTo(PageManager.class);
Page targetPage = pageManager.getPage(relation.getLiveCopy().getPath());
String targetLanguage = targetPage.getName();
Node sourceNode = source.adaptTo(Node.class);
PropertyIterator pi = sourceNode.getProperties();
while (pi.hasNext()) {
Property property = pi.nextProperty();
if(property.isMultiple()) {
for(Value value : property.getValues()) {
processSingleValue(value, resolver, target, pageManager, targetLanguage);
}
} else {
processSingleValue(property.getValue(), resolver, target, pageManager, targetLanguage);
}
}
}
private void processSingleValue(Value value, ResourceResolver resolver, Resource target, PageManager pageManager, String targetLanguage) throws RepositoryException, WCMException {
if(value.getType() != PropertyType.STRING) {
return;
}
String ctaPath = value.getString();
if(ctaPath == null ||!ctaPath.contains("/content/site-com")) {
return;
}
Matcher pathMatcher = CONTENT_PATH_PATTERN.matcher(ctaPath);
while (pathMatcher.find()) {
Resource cta = resolver.getResource(pathMatcher.group());
adjustReferences(pageManager, cta, target, targetLanguage);
}
}
private void adjustReferences(PageManager pageManager, Resource cta, Resource target, String targetLanguage) throws RepositoryException, WCMException {
RangeIterator relationshipsIterator = relationshipManager.getLiveRelationships(cta, null, null);
while (relationshipsIterator.hasNext()) {
LiveRelationship relationship = (LiveRelationship) relationshipsIterator.next();
Page ctaLiveCopyPage = pageManager.getPage(relationship.getLiveCopy().getPath());
if (isSubjectForReferencesAdjustment(ctaLiveCopyPage, targetLanguage)) {
new ReferenceSearch().adjustReferences(target.adaptTo(Node.class),
relationship.getSourcePath(), relationship.getTargetPath(), true, Collections.emptySet());
}
}
}
private boolean isSubjectForReferencesAdjustment(Page ctaLiveCopyPage, String targetLanguage) {
return ctaLiveCopyPage != null && ctaLiveCopyPage.getPath().endsWith(targetLanguage);
}
}
}
</sitereferencesupdateactionfactory.sitereferencesupdateaction><!--?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="cq:Page">
<jcr:content cq:defaultview="html" cq:rolloutconfigid53="cq:trigger=rollout#updateContent/status=true####" cq:template="/libs/wcm/msm/templates/rolloutconfig" cq:trigger="rollout" jcr:description="Updating CTA and other references for XF on rollout trigger" jcr:primarytype="nt:unstructured" jcr:title="Update Site references in XF config" sling:resourcetype="wcm/msm/components/rolloutconfig">
<referencesupdateps jcr:primarytype="cq:LiveSyncAction"></referencesupdateps>
</jcr:content>
</jcr:root>We will create a new client lib “xf-rollout” .content.xml:
<!--?xml version="1.0" encoding="UTF-8"?-->
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" jcr:primarytype="cq:ClientLibraryFolder" categories="[cq.experiencefragments.authoring]" dependencies="[granite.jquery]"></jcr:root>(function ($, $document) {
var ROLLOUT_CONFIGS_SELECT = "coral-select[name='cq:rolloutConfigs']";
$(document).on("dialog-ready", dlgReadyHandler);
function dlgReadyHandler() {
if (_.isEmpty($("[value='createLiveCopy'][name='cmd']"))) {
return;
}
var srcPath = $("input[name='srcPath']").attr("value");
if(!srcPath || !isSiteComXF(srcPath)) {
return;
}
selectItems(ROLLOUT_CONFIGS_SELECT, [
"/etc/msm/rolloutconfigs/default",
"/etc/msm/rolloutconfigs/updateSiteReferences"], true);
}
function isSiteComXF(srcPath) {
var result = false;
$.ajax(
{
url: srcPath.replace(".html", ".infinity.json"),
type: 'GET',
async: false,
success: function (data) {
var content = data["jcr:content"];
if (!content || !content["cq:template"]) {
return;
}
result = (content["cq:template"] === "/conf/site-com/settings/wcm/templates/experience-fragment");
}
}
);
return result;
}
function selectItems(selector, values, action) {
var sel = $(selector);
sel.each(function(idx, select){
select.items.getAll().forEach(function(item, idx){
if(values.includes(item.value)){
item.selected = action;
}
});
});
}
})($, $(document));

.png)





