Update Link References inside Experience fragment on Rollout

 


As we all know that when we create a live copy of experience fragment or rollout any experience fragments the site's link reference inside it does not get updated as it gets updated in a page. This feature is not available OOTB.

In order to achieve that we can create a custom rollout config and can achieve our use case.

Steps :

Add Synchronization Actions to the Rollout Configuration

  • Go to /apps/msm , create folder named rolloutconfig

  • Under this node create another node
    •  jcr:primaryType ----- cq:RolloutConfig     
    • Title: Update XF Site Links
    • Name: updateXFlinks
    • cq:trigger: rollout

  • Under this node now create another node
    • NamelinksUpdateXF
    • Typecq:LiveSyncAction



Implement LiveActionFactory :

The following LiveActionFactory class implements a LiveAction which updates the link refence in experience fragment on creating a live copy or rollout from master experience fragment. I have excluded countryselector component from update , you can update that part according to your needs.

import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.adobe.cq.xf.ExperienceFragmentsServiceFactory;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.WCMException;
import com.day.cq.wcm.commons.ReferenceSearch;
import com.day.cq.wcm.msm.api.LiveActionFactory;
import com.day.cq.wcm.msm.api.LiveRelationship;
import com.day.cq.wcm.msm.api.LiveRelationshipManager;
import com.day.cq.wcm.msm.commons.BaseAction;
import com.day.cq.wcm.msm.commons.BaseActionFactory;
import com.demo.core.constants.Constants;
import com.demo.core.utils.CommonUtils;
/**
 * A factory to update experience fragments link on rollout.
 */
@Component(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 BaseActionFactory<BaseAction> {
/** The Constant LIVE_ACTION_CLASS_NAME. */
public static final String LIVE_ACTION_CLASS_NAME = "XFReferencesUpdateAction";
/** The Constant LIVE_ACTION_NAME. */
public static final String LIVE_ACTION_NAME = "linksUpdateXF";
/** The Constant CONTENT_PATH_REGEXP. */
private static final String CONTENT_PATH_REGEXP = "/content/(we-retail)[\\w\\-/]*";
/** The Constant CONTENT_PATH_PATTERN. */
private static final Pattern CONTENT_PATH_PATTERN = Pattern.compile(CONTENT_PATH_REGEXP);
/** The relationship manager. */
@Reference
private LiveRelationshipManager relationshipManager;
/** The experience fragments service factory. */
@Reference
ExperienceFragmentsServiceFactory experienceFragmentsServiceFactory;
/**
 * New action instance.
 *
 * @param valueMap the value map
 * @return the XF references update action
 */
@Override
protected XFReferencesUpdateAction newActionInstance(ValueMap valueMap) {
return new XFReferencesUpdateAction(valueMap, this);
}
/**
 * Creates a new XFReferencesUpdateAction object.
 *
 * @return the string
 */
public String createsAction() {
return LIVE_ACTION_NAME;
}
/**
 * The Class XFReferencesUpdateAction.
 */
class XFReferencesUpdateAction extends BaseAction {
/** The log. */
private Logger log = LoggerFactory.getLogger(XFReferencesUpdateAction.class);
/**
 * Instantiates a new XF references update action.
 *
 * @param valueMap the value map
 * @param factory  the factory
 */
public XFReferencesUpdateAction(ValueMap valueMap, XFReferencesUpdateActionFactory factory) {
super(valueMap, factory);
}
/**
 * Do execute.
 *
 * @param source       the source
 * @param target       the target
 * @param relation     the relation
 * @param resetRollout the reset rollout
 * @throws WCMException the WCM exception
 */
@Override
public void doExecute(Resource source, Resource target, LiveRelationship relation, boolean resetRollout)
throws WCMException {
ResourceResolver resolver = target.getResourceResolver();
PageManager pageManager = resolver.adaptTo(PageManager.class);
Page targetPage = pageManager.getPage(relation.getLiveCopy().getPath());
String sourcePath = source.getPath();
Resource sourceRoot = resolver.getResource(sourcePath);
Node sourceNode = sourceRoot.adaptTo(Node.class);
try {
PropertyIterator pi = sourceNode.getProperties();
while (pi.hasNext()) {
Property property = pi.nextProperty();
if (property.isMultiple()) {
for (Value value : property.getValues()) {
processSingleValue(value, resolver, target, pageManager, targetPage);
}
} else {
processSingleValue(property.getValue(), resolver, target, pageManager, targetPage);
}
}
} catch (RepositoryException e) {
log.error("Repository Exception", e);
}
}
/**
 * Process single value.
 *
 * @param value       the value
 * @param resolver    the resolver
 * @param target      the target
 * @param pageManager the page manager
 * @param targetPage  the target page
 * @throws RepositoryException the repository exception
 */
private void processSingleValue(Value value, ResourceResolver resolver, Resource target,
PageManager pageManager, Page targetPage) throws RepositoryException {
if (value.getType() != PropertyType.STRING) {
return;
}
String ctaPath = value.getString();
if (ctaPath == null || !ctaPath.startsWith("/content/we-retail")) {
return;
}
Matcher pathMatcher = CONTENT_PATH_PATTERN.matcher(ctaPath);
while (pathMatcher.find()) {
Resource cta = resolver.getResource(pathMatcher.group());
adjustReferences(pageManager, cta, target, targetPage);
}
}
/**
 * Adjust references.
 *
 * @param pageManager the page manager
 * @param cta         the cta
 * @param target      the target
 * @param targetPage  the target page
 * @throws RepositoryException the repository exception
 */
private void adjustReferences(PageManager pageManager, Resource cta, Resource target, Page targetPage)
throws RepositoryException {
if (Objects.nonNull(target) && !target.getPath().contains("countryselector") && Objects.nonNull(cta)) {
String[] array = targetPage.getPath().replace("/content/experience-fragments/we-retail/", StringUtils.EMPTY)
.split(Constants.SLASH);
Page ctaPage = pageManager.getPage(cta.getPath());
String targetCountryLanguage = array[0] + Constants.SLASH + array[1];
String ctaCountryLanguage = CommonUtils.getCountryCode(ctaPage) + Constants.SLASH
+ CommonUtils.getLanguageCode(ctaPage);
String targetPagePath = cta.getPath().replace(ctaCountryLanguage, targetCountryLanguage);
new ReferenceSearch().adjustReferences(target.adaptTo(Node.class), cta.getPath(), targetPagePath, false,
Collections.emptySet());
}
}
/**
 * Handles.
 *
 * @param source       the source
 * @param target       the target
 * @param relation     the relation
 * @param resetRollout the reset rollout
 * @return true, if successful
 * @throws RepositoryException the repository exception
 * @throws WCMException        the WCM exception
 */
@Override
protected boolean handles(Resource source, Resource target, LiveRelationship relation, boolean resetRollout)
throws RepositoryException, WCMException {
return source.getPath().contains("experience-fragments");
}
}
}

Code from CommonUtils:

public static String getCountryCode(final Page currentPage) {
Page languagePage = getLanguagePage(currentPage);
final String country = "us";
if (Objects.nonNull(languagePage) && Objects.nonNull(languagePage.getParent())
&& Objects.nonNull(languagePage.getParent().getName())) {
return languagePage.getParent().getName();
}
return country;
}

public static Page getLanguagePage(final Page currentPage) {
if (Objects.nonNull(currentPage)) {
return currentPage.getAbsoluteParent(3);
}
return null;
}

public static String getLanguageCode(final Page currentPage) {
if (Objects.nonNull(currentPage)) {
return currentPage.getLanguage(Boolean.FALSE).getLanguage();
}
return StringUtils.EMPTY;
}

Now while rolling out or creating your experience fragment choose Update XF Site Links rollout config along with Standard rollout config, then Save and then rollout or create live copy. You will see the Link references inside Experience fragment are updated and pointing correctly.



 Hope this helps!!

 Happy Coding 🙏


If you like my post and find it helpful, you can buy me a coffee.

Comments