Externalizer and MultiSiteExternalizer

Externalizer is an out of the box OSGI service which helps us to externalize our URL. This is the same URL which we can share externally or with public.

Suppose, we are sending a mail to end user and as part of that mail we want to share a link to update password. While authoring we just author relative path as part of component or page properties e.g. /content/practice/us/en/update-password. In email, we cannot share /content/practice/us/en/update-password as a URL to end user without having domain.

Externalizer helps us to create URL having domain part of it such as http://practice.abc/content/practice/us/en/update-password from /content/practice/us/en/update-password

Authored URL: /content/practice/us/en/update-password
Externalized URL: http://practice.abc/content/practice/us/en/update-password

Follow below steps to implement Externalizer for single site heirarchy as part of current implementation:

  1. Update OOTB Externalize OSGI configuration com.day.cq.commons.impl.ExternalizerImpl.xml as part of our code base as shown below:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
      xmlns:jcr="http://www.jcp.org/jcr/1.0"
      jcr:primaryType="sling:OsgiConfig"
      externalizer.contextpath=""
      externalizer.host=""
      externalizer.encodedpath="{Boolean}false"
      externalizer.domains="[author\ https://author-practice.adobecqms.net, publish\ practice.abc]"/>

2. Create Sling Model Java Class to generate end User URL for both author and publish Link:

package com.javadoubts.core.models;

import com.day.cq.commons.Externalizer;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

@Model(adaptables = Resource.class)
public class ButtonModel {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    @Optional
    protected String link;

    @SlingObject
    ResourceResolver resourceResolver;

    @OSGiService
    Externalizer externalizer;

    private String publishLink;

    private String authorLink;

    @PostConstruct
    protected void init() {
        publishLink = externalizer.publishLink(resourceResolver,link);
        authorLink = externalizer.authorLink(resourceResolver,link);
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public String getPublishLink() {
        return publishLink;
    }

    public void setPublishLink(String publishLink) {
        this.publishLink = publishLink;
    }

    public String getAuthorLink() {
        return authorLink;
    }

    public void setAuthorLink(String authorLink) {
        this.authorLink = authorLink;
    }
}

3. Create a component to author link having below sightly code within it as shown below:

Link – ${button.link}
Author Link – ${button.authorLink}
Publish Link – ${button.publishLink}

4. Below is the output of the link component:

Link β†’ This is authorable path browser link field.

Author Link β†’ This is showing externalized Author URL having author domian.

Publish Link β†’ This is showing externalized Publish URL having publish domain.

Multisite Externalizer

We can support Multisite domains using Multisite Externalizer architecture.

Using Multisite Externalizer we can have site hierarchy specific domain. This means for every site hierarchy we can attach a domain as shown below.

MultiSiteExternalizer Over all Flow

Let’s try to understand what we want to achieve out of MultiSite Externalizer.

As part of current blog, there is an authorable field named as link having value /content/practice/us/en/home present within the component. As the URL starting with /content/practice, Sling model will look for /content/practice mapping in custom MultiSiteExternalizer OCD Configuration and try to find a matched key as practice which is followed by colon(:).

On the basis of matched key and with the help of OOTB Day CQ Link Externalizer OSGI configuration, it will again look for practice domain mapping and look for matched domain as http://practice.com.

After finding domain, it will concatenate the domain(http://practice.com) with the authored URI(/content/practice/us/en/home) using externalizer service externalLink() method.

Final output: http://practice.com/content/practice/us/en/home

Implementation

We are going to use R6 annotation to implement Multisite Externalizer. To read more about R6 annotation please follow this link.

Follow below steps to create Multisite Externalizer:

  1. Create a custom OSGI configuration com.javadoubts.core.services.impl.MultisiteExternalizerOCDServiceImpl.xml to define a mapping as shown below.

For example, According to one of the mapping /content/practice:practice. URL starting with /content/practice is having practice as a key.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
  jcr:primaryType="sling:OsgiConfig"
  mapping="[/content/practice:practice,/content/test:test,/content/javadoubts:javadoubts]"/>

2. Create Externalize OSGI configuration com.day.cq.commons.impl.ExternalizerImpl.xml as shown below having key and domain mapping.

For example, we have practice http://practice.com as one of the key domain mapping. Key found as practice as part of 1st step will have matched domain as http://practice.com

3. Create OCD configuration file as MultisiteExternalizeOCDConfiguration.java

package com.javadoubts.core.services;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(
    name = "MultisiteExternalizer OCD Configuration",
    description = "MultisiteExternalizer OCD Configuration description"
)
public @interface MultisiteExternalizeOCDConfiguration {

    @AttributeDefinition(
            name = "String[] of Site and Domain Mapping",
            description = "String[] of Site and Domain Mapping",
            type = AttributeType.STRING)
    String[] mapping();

}

3. Create below interface named as MultisiteExternalizerOCDService.java having and a public method getMapping() method.

package com.javadoubts.core.services;

public interface MultisiteExternalizerOCDService {
    public String[] getMapping();
}

4. Create below class MultisiteExternalizerOCDServiceImpl.java which implements MultisiteExternalizerOCDService.java interface and implement getMapping() method.

package com.javadoubts.core.services.impl;

import com.javadoubts.core.services.MultisiteExternalizeOCDConfiguration;
import com.javadoubts.core.services.MultisiteExternalizerOCDService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.Designate;

@Component(service = MultisiteExternalizerOCDService.class, immediate = true)
@Designate(ocd = MultisiteExternalizeOCDConfiguration.class)
public class MultisiteExternalizerOCDServiceImpl implements MultisiteExternalizerOCDService {

    private String[] mapping;

    @Activate
    protected void activate(MultisiteExternalizeOCDConfiguration config) {
       this.mapping = config.mapping();
    }

    @Override
    public String[] getMapping() {
        return mapping;
    }
}

5. Create or use earlier created Sling Model Java Class to generate end User externalized URL depending on domain and site hierarchy:

package com.javadoubts.core.models;

import com.day.cq.commons.Externalizer;
import com.javadoubts.core.services.MultisiteExternalizerOCDService;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

@Model(adaptables = Resource.class)
public class ButtonModel {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Inject
    @Optional
    protected String link;

    @SlingObject
    ResourceResolver resourceResolver;

    @OSGiService
    Externalizer externalizer;

    @OSGiService
    MultisiteExternalizerOCDService multisiteExternalizerOCDService;


    private String externalizeLink;
    @PostConstruct
    protected void init() {

        List<String> paths = new ArrayList<>();
        List<String> domains = new ArrayList<>();

        /*
          This will create a list of paths and domains separated by 
          colon(:) which is define as part of 
          com.javadoubts.core.services.impl.MultisiteExternalizerOCDServiceImpl.xml
          OSGI Configuration.          

          /content/practice:practice,
          /content/test:test,
          /content/javadoubts:javadoubt
        */        
        String[] mappings = multisiteExternalizerOCDService.getMapping();
        if (ArrayUtils.isNotEmpty(mappings)) {
            for (String mapping: mappings) {
                String[] parts = StringUtils.split(mapping, ":");
                if (parts.length == 2 ) {
                    paths.add(parts[0]);
                    domains.add(parts[1]);
                }
            }
        }

        String domain = getMappedDomain(paths, domains, link);

        // It will create externalize path depending on domain key.
        externalizeLink = externalizer.externalLink(
            resourceResolver, domain, link);
    }

    /* 
      Function will return key for domain mapping. 
      e.g. practice for /content/practice site hierarchy.
    */
    private String getMappedDomain(List<String> paths, List<String> domains, String url) {
        String domain = null;
        for (int i = 0; i < paths.size(); i++) {
            String candidatePath = paths.get(i);
            if (StringUtils.isNotEmpty(link) && link.startsWith(candidatePath)) {
                domain = domains.get(i);
                break;
            }
        }

        if (StringUtils.isBlank(domain)) domain = "publish";

        return domain;
    }


    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public String getExternalizeLink() {
        return externalizeLink;
    }

    public void setExternalizeLink(String externalizeLink) {
        this.externalizeLink = externalizeLink;
    }
}

6. Create a component to author link having below sightly code within it as shown below:

Link – ${button.link}
Externalize Link – ${button.externalizeLink}

7. Below is the component output where it is using http://practice.com as domain depending on site hierarchy /content/practice/us/en/home starting with /content/practice.

Imran Khan

Specialist Master (Architect) with a passion for cutting-edge technologies like AEM (Adobe Experience Manager) and a proven track record of delivering high-quality software solutions.

  • Languages: Java, Python
  • Frameworks: J2EE, Spring, Struts 2.0, Hibernate
  • Web Technologies: React, HTML, CSS
  • Analytics: Adobe Analytics
  • Tools & Technologies: IntelliJ, JIRA

🌐 LinkedIn

πŸ“ Blogs

πŸ“§ Imran Khan