Friday, October 8, 2021

AEM(Adobe Experience Manager): Extend Experience Fragment model to support localized header/footer with custom site structure

AEM(Adobe Experience Manager): Extend Experience Fragment model to support localized header/footer with custom site structure

One of my earlier blogs talked about how to reuse the same editable templates with multiple sites, how to use the XF localization feature to enable different headers and footers for the websites build on the same template - there are multiple approaches to achieve this e.g. dedicated template for sites, enabling the header at page level rather in template level but we enabled the header in template level and used the localization feature to support the header variants.


The XF localization feature works well if the content(/content/mysite1/us/en) and experience fragment(/content/experience-fragments/mysite1/us/en) structures follows the same pattern.

Recently we had a use case to reuse the pages created with editable template under the legacy content path with localized headers/footers(independent websites)  - as  I said earlier this can be achieved in multiple ways e.g. create a dedicated template but we decided to extend the Experience Fragment localization logic with delegation pattern.

The global header XF is enabled under - /content/experience-fragments/mysite1/us/en/site/header/master and the content is under /content/mysite1/us/en - the header XF is embeded in the template structure, this supports localization by just enabling the copy of header XF and the content(e.g. XF - /content/experience-fragments/mysite1/fr/fr/site/header/master, Site - /content/mysite1/fr/fr). The need is to reuse the same content under(with same template) the legacy content path(e.g /content/mysite2/en-US), the default XF localization feature will not help here to enable the localized header for the pages under /content/mysite2/en-US as the the legacy content path structure is not matching with the global header embeded in the template.

The localization logic is enabled in the getLocalizedFragmentVariationPath() method of the XF core component model(com.adobe.cq.wcm.core.components.internal.models.v1.ExperienceFragmentImpl) , the Delegation pattern helps us to override the required methods(getLocalizedFragmentVariationPath()) from Core Component Models(ExperienceFragment) to enable the custom logic.

In a normal delegation process,  all the public methods from the original interface are added into the custom class and override or delegate the calls to the original class, this approach may create challenges while the existing methods are changed or new methods are introduced. The Lombok Java package helps here - inject all the public interface methods into the custom class and override only the required methods(this will help to avoid the challenges related to the version upgrade)

Let us now see how to use the Delegate pattern to override the Experience Fragment Model to support our use case.

The assumption is the legacy content is under three-level e.g. /content/mysite2/en-US - the logic can be changed based on your content hierarchy and the header will be placed under /content/experience-fragments/mysite2/en-US/site/header/master

Experience Fragment


As a first step, Create a custom XF Model in your core module - CustomExperienceFragmentImpl and associate it with the custom(proxy) XF component resource type.

@Model(adaptables = { Resource.class,
SlingHttpServletRequest.class }, adapters = ExperienceFragment.class, resourceType = CustomExperienceFragmentImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class CustomExperienceFragmentImpl implements ExperienceFragment {

public static final String RESOURCE_TYPE = "mysite/components/experiencefragment"; // Specify the resourceType of the custom(Proxy) XF component
  //used to embed the headers and footers in to the template

Now create the delegate for the parent resource type model through @Self and @Via annotations

The @Delegate is the Lombok java library annotation to inject the delegated methods at compile-time, the exclusion can be defined- the methods that will be overridden by the custom class, in our case only the getLocalizedFragmentVariationPath method is overridden and the remaining methods are delegated.

@Self 
@Via(type = ResourceSuperType.class)
@Delegate(excludes = DelegationExclusion.class) // Define the the methods that should be exclude from the delegation
private ExperienceFragment delegate;


private interface DelegationExclusion { 
String getLocalizedFragmentVariationPath();
}

In the compile-time Lombok injects all the public methods into the custom class and delegate those calls to the parent class

public String getAppliedCssClasses() { return this.delegate.getAppliedCssClasses(); } public ComponentData getData() { return this.delegate.getData(); } etc.

Override getLocalizedFragmentVariationPath() with custom logic, the logic will check if there is any valid header/footer XF available in the defined location for the content path, if so the header/footer XF fragment path is returned. If there is no matching header the call will be delegated to the parent ExperienceFragment implementation. If required the call can be directly delegated to the parent class for the specific content path(the content path in which the new content structure is followed).

Add the latest Lombok library maven dependency to your core module

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.20</version>
                <scope>provided</scope>
            </dependency>

Deploy the updated core module to the AEM server

Now, when the user accesses the content under /content/mysite2/en-US or /content/mysite2/fr-FR, the custom logic looks for the site-specific header XF under  /content/experience-fragments/mysite2/en-US/site/header/master or /content/experience-fragments/mysite2/fr-FR/US/site/header/master and returns the XF path if available, if not delegate the calls to the parent XF Model that applies the default logic(the XF path configured in the template will be displayed).

Refer to the below gist for the complete model(modify the model based on your use cases)



Additionally, if required enable page-level authoring fields(XF browsers) so that the authors can override the XF path configured in the template level for a specific websites.

Change the model logic to send back the overridden XF path

if (fragmentVariationPath.contains(XF_HEADER_VARIATION)) {

InheritanceValueMap ivm = new HierarchyNodeInheritanceValueMap(currentPage.getContentResource());
String headeroverrideXF=ivm.getInherited("headeroverride", String.class);
if(StringUtils.isNoneEmpty(headeroverrideXF) && resourceExists(headeroverrideXF))
{
return headeroverrideXF; 
}
}

The delegation pattern helps us to override the sling models to enable custom functionalities based on your use cases, using the Lombok java library helps us to simplify the generation of delegation methods - the Lombok library injects the delegation methods during compilation.

I want to highlight an issue faced while implementing this - delegate for all the public methods were not injected by Lombok library, the actual issue was an old version of Core Component dependency was added through aem-sdk-api but the latest Core Component version was enabled in the server(6.5), the issue got resolved after fixing the dependency issue(the XF was displaying blank data due to this issue).


No comments:

Post a Comment