AEM Experience Fragments: URL Externalization with Adobe Target

Exadel Digital Experience Team Tech Tips April 22, 2021 6 min read

In most cases, internal links in HTML are relative links, but there may be cases when custom components provide full URLs in HTML. By default, such URLs are ignored. In this article we will show how to deal with this type of link so that they are sent into target in the correct format.

The Basics

Using the “Export to Target” feature you can create an XF, add components to it, and then export it as an Adobe Target Offer. This feature can be enabled on an author instance of AEM. Since Experience Fragments are exported from the AEM author instance, you need to make sure that any references within the XF are externalized for web delivery. This requires a valid Adobe Target Configuration and configurations for the link externalizer. The Link Externalizer is used to determine the URLs for the HTML version of the Target Offer. Adobe Target requires all links inside the Target HTML Offer to be publicly accessed, so you need to publish the XF itself and all its links before pushing to Target. When a Target HTML Offer is being constructed, a request is sent to a custom Sling selector in AEM. This selector is called .atoffer.nocloudconfigs.html. After you generate the HTML page, the Sling Rewriter pipeline makes modifications to the output:

  • The HTML, head, and body elements are replaced with div elements. The meta, noscript, and title elements are removed.
  • AEM modifies any internal links present in HTML so that they point to a published resource. AEM follows this pattern for attributes of HTML elements:
    • src attributes
    • href attributes
    • *-src attributes (like data-src, custom-src, etc.)
    • *-href attributes (like data-href, custom-href, img-href, etc.)

This is an extension point offered to clients in order to allow link rewriting for the Export to Target feature. Services implementing this interface will be called during the Export to Target offer content generation for HTML.

When an Export to Target call is made in the AEM Experience Fragment console, the body of the HTML content is generated through a request to an XF with the selector atoffer.html. Before this HTML content is sent to Target, we would need to rewrite all the links so that they can be accessed publicly. We already rewrote these links using Externalizer, but if you need more customization, a service can implement this interface to make that possible.

Currently, only one ExperienceFragmentLinkRewriterProvider for an XF is permitted. If there are multiple providers for an XF, only the one with the highest priority is chosen.

Priority is determined by the providers getPriority() method. If there are several ExperienceFragmentLinkRewriterProvider with the same priority, then only one will be chosen.

Example

A new rewrite config /Site-com/ui.apps/src/main/content/jcr_root/apps/site-com/config/rewriter/xf-target/.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" jcr:primarytype="nt:unstructured" contenttypes="[text/html]" enabled="{Boolean}true" generatortype="htmlparser" order="6000" serializertype="htmlwriter" selectors="[atoffer]" extensions="[html]" transformertypes="[linkchecker,mobile,mobiledebug,contentsync,versioned-clientlibs,adobe-target-offer]">
   <transformer-mobile jcr:primarytype="nt:unstructured" component-optional="{Boolean}true">
   <transformer-mobiledebug jcr:primarytype="nt:unstructured" component-optional="{Boolean}true">
   <transformer-contentsync jcr:primarytype="nt:unstructured" component-optional="{Boolean}true">
   <generator-htmlparser jcr:primarytype="nt:unstructured" includetags="[A,/A,IMG,AREA,FORM,BASE,LINK,SCRIPT,BODY,/BODY]">
</generator-htmlparser></transformer-contentsync></transformer-mobiledebug></transformer-mobile></jcr:root>
See more See less
  • Make sure “atoffer” selector is added
  • Make sure “adobe-target-offer” transformer type is added
  • Make sure order parameter is higher than the order of your default rewrite config

/site-com/core/src/main/java/com/wcm/site/xf/XFLinkRewriter.java:

package com.wcm.site.xf;
 
import com.adobe.cq.xf.ExperienceFragmentLinkRewriterProvider;
import com.adobe.cq.xf.ExperienceFragmentVariation;
import com.adobe.cq.xf.ExperienceFragmentsConstants;
import com.wcm.site.model.LinkUpdateParams;
import com.wcm.site.services.LinkExternalizeService;
import com.wcm.site.util.LinkUtils;
import static com.wcm.site.util.LinkUtils.joinAsPath;
import com.wcm.site.util.ServiceUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
 
@Component(immediate = true, service = ExperienceFragmentLinkRewriterProvider.class)
@ServiceDescription("XF to Target link rewriter")
@Designate(ocd = XFLinkRewriter.XFLinkRewriterConfig.class)
public class XFLinkRewriter implements ExperienceFragmentLinkRewriterProvider {
 
    @ObjectClassDefinition(name = "XF Link Rewriter Config")
    public @interface XFLinkRewriterConfig {
    }
 
    @Reference
    private LinkExternalizeService linkExternalizeService;
 
    @Reference
    private ResourceResolverFactory resourceResolverFactory;
 
 
    @Override
    public String rewriteLink(String link, String tag, String attribute) {
        if (StringUtils.isBlank(link)) {
            return link;
        }
 
        try (ResourceResolver resourceResolver = ServiceUtils.createResourceResolver(resourceResolverFactory)){
            if(resourceResolver == null) {
                return link;
            }
            String path = getPath(resourceResolver, link);
            if(LinkUtils.isExternal(path)) {
                return path;
            }
 
            return linkExternalizeService.publishLink(resourceResolver, path);
        }
    }
 
    @Override
    public boolean shouldRewrite(ExperienceFragmentVariation experienceFragmentVariation) {
        return StringUtils.startsWith(experienceFragmentVariation.getPath(), “/content/experience-fragments/to-target”);
    }
 
    @Override
    public int getPriority() {
        return 0;
    }
 
    private String getPath(ResourceResolver resourceResolver, String url) {
        String authorDomain = linkExternalizeService.authorLink(resourceResolver, StringUtils.EMPTY);
        return StringUtils.removeStart(StringUtils.removeStart(url, authorDomain), LinkUtils.removeScheme(authorDomain));
    }
 
}
See more See less

In this example we show how we rewrote author links with the relative ones. You may face your own unique issues, but this basic approach will be useful to correct the target data.

Author: Iryna Ason

Was this article useful for you?

Get in the know with our publications, including the latest expert blogs