April 27, 2020
Estimated Post Reading Time ~

Different approaches to perform Vanity/Redirect URL management in AEM

In Adobe Experience Manager there is no centralized management UI to manage the Vanity/Redirect URL's also the vanity/Redirects are added into multiple places like Apache and AEM.

This post will explain the different approaches to manage the Vanity/Redirect URL through centralized management UI.
Option1: Manage the Vanity URL's through VanityPath page property


The same approach can be followed to manage the redirect path enabled in AEM

Vanity URL's are handled in AEM so all the request reaches AEM publisher

Option2 - Manage the Vanity/redirects through AEM UI on XML and redirect through Java Filter


Store the vanity and redirects mapping in an XML file within AEM repository

<rules>
 <rule>
 <siteName></siteName>
 <code></code>
 <sourcePath></sourcePath>
 <target></target>

 </rule>
<rules>

  • Build a UI application to manage the redirects in the file - modify/remove and add new rules
  • Replicate the mapping XML to the publisher on every modification through replication API
  • Build a Java filter that will redirect to the target URL if the vanity path is defined in the XML file(the filter should be restricted only required path)
  • Whitelist the vanitypaths in Apache – Expose the vanitypaths through custom URL(Servlet expose the list of vanity URLs by parsing the XML) and configure the URL in Apache for whitelisting(Refer https://helpx.adobe.com/experience-manager/dispatcher/using/dispatcher-configuration.html#EnablingAccesstoVanityURLsvanityurls for apache configuration, the vanity list URL should be custom)

Vanity/Redirects are handled in AEM so all the request reaches AEM publisher

Sample Rule file:
<rules>
    <rule>
        <sitename>test</sitename>
        <type>302</type>
        <target>http://localhost:8080/content/geometrixx-outdoors/en.html</target>
        <source>/testVanity</source>
    </rule>
    <rule>
        <sitename>test</sitename>
        <type>302</type>
        <target>http://localhost:8080/content/geometrixx-outdoors/en/men.html</target>
        <source>/testVanity1</source>
    </rule>
</rules>

Add the vanity_urls configuration to the farm:

/vanity_urls {
 /url "/services/getRedirects.html"
 /file "/tmp/vanity_urls"
 /delay 30
}

Add a filter rule in the dispatcher to allow the vanity URL to be called on Publish instance:
/0100 { /type "allow" /url "/services/getRedirects.html" }

Add a caching rule to prevent caching of this URL:
/0001 { /type "deny" /glob "/services/getRedirects.html" }

Sample GetRedirects Servlet:

import java.io.IOException;
import java.io.PrintWriter;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Session;
import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

@SuppressWarnings("serial")
@SlingServlet(paths= "/services/getRedirects")
public class GetRedirects extends SlingSafeMethodsServlet {

 @Reference
 private ResourceResolverFactory resolverFactory;

 @Override
 protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
 throws ServletException, IOException {

 String redirectMapPath="/content/geometrixx-outdoors/en/redirectmap.xml/jcr:content";
 Session session = null;

  resp.setContentType("text/html");
  PrintWriter pw = resp.getWriter();

 // this is for demo purpose
     //the redirect should be parsed and stored in HashMap and updated on every modification

      //Servlet should list the redirects from HashMap

 try {

  ResourceResolver resourceResolver = resolverFactory.getAdministrativeResourceResolver(null);
       session=resourceResolver.adaptTo(Session.class);
      // create a new DocumentBuilderFactory
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                  
          if(session.nodeExists(redirectMapPath)) {
              Node node = session.getNode(redirectMapPath);
              if(node.hasProperty("jcr:data")) {
                  Property jcrData = node.getProperty("jcr:data");
                  DocumentBuilder builder = factory.newDocumentBuilder();           
                  InputSource is = new InputSource(jcrData.getBinary().getStream());
                  Document doc = builder.parse(is);
                  NodeList rules=doc.getElementsByTagName("rule");
                  for (int i = 0; i < rules.getLength(); i++) {
                   Element rule = (Element) rules.item(i);                 
                   NodeList sourceElements = rule.getElementsByTagName("source");
                      Element source = (Element) sourceElements.item(0);
                      NodeList sourceChildren = source.getChildNodes();
                      String sourceValue = sourceChildren.item(0).getNodeValue(); 
                      pw.println(sourceValue);
   
                  }
              }
          }

 } catch (Exception e) {
 // TODO Auto-generated catch block

 e.printStackTrace();
 }finally {
 if(session!=null)
 {
 session.logout();
 }
 }
 }

}

Sample RedirectFilter.java:

import java.io.IOException;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Session;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingFilter;
import org.apache.felix.scr.annotations.sling.SlingFilterScope;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * Simple servlet filter component that logs incoming requests.
 */
@SlingFilter(order = -700, scope = SlingFilterScope.REQUEST)
public class RedirectFilter implements Filter {

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

    @Reference
    ResourceResolverFactory resourceResolverFactory;

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,

            final FilterChain filterChain) throws IOException, ServletException {
     
     
     String redirectMapPath="/content/geometrixx-outdoors/en/redirectmap.xml/jcr:content";
        final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
        final SlingHttpServletResponse  slingResponse = (SlingHttpServletResponse ) response;     
        Session session=null;
    
        // this is for demo purpose
        //the redirect should be parsed and stored in HashMap and updated on every modification
        //Filter should verify the the redirects in HashMap and redirect accordingly
       try {
        
        ResourceResolver resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
        session=resourceResolver.adaptTo(Session.class);
     // create a new DocumentBuilderFactory
           DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
           String requestPath=slingRequest.getPathInfo();
           logger.error("requestPath:"+requestPath);

        if(session.nodeExists(redirectMapPath)) {
            Node node = session.getNode(redirectMapPath);
            if(node.hasProperty("jcr:data")) {
                Property jcrData = node.getProperty("jcr:data");
                DocumentBuilder builder = factory.newDocumentBuilder();           
                InputSource is = new InputSource(jcrData.getBinary().getStream());
                Document doc = builder.parse(is);
                NodeList rules=doc.getElementsByTagName("rule");
                for (int i = 0; i < rules.getLength(); i++) {
                 Element rule = (Element) rules.item(i);                 
                 NodeList sourceElements = rule.getElementsByTagName("source");
                    Element source = (Element) sourceElements.item(0);
                    NodeList sourceChildren = source.getChildNodes();
                    String sourceValue = sourceChildren.item(0).getNodeValue();
                    logger.error("sourceValue:"+sourceValue);                 
                 
                    if(sourceValue.equals(requestPath))
                    {                  
                 
                    NodeList targetElements = rule.getElementsByTagName("target");
                 Element target = (Element) targetElements.item(0);
                 NodeList targetChildren = target.getChildNodes();
                 String targetValue = targetChildren.item(0).getNodeValue();                     
                 logger.error("targetValue:"+targetValue);
              
                 NodeList typeElements = rule.getElementsByTagName("type");
               Element type = (Element) typeElements.item(0);
               NodeList typeChildren = type.getChildNodes();
               String typeValue = typeChildren.item(0).getNodeValue();
               logger.error("typeValue:"+typeValue);
            

               if(typeValue.equals("301"))
               {
                 slingResponse.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); 
               }else
               {
                slingResponse.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); 
               }                  
            
               slingResponse.setHeader("Location", targetValue);
            
               return;
                    }

 }
             
                logger.error("Vanity Path not defined, continuing the filter chan in");
             filterChain.doFilter(request, response); 
             return;

             
            }
        }
       
       } catch (Exception ex) {
          ex.printStackTrace();
       }finally {
 if(session!=null)
 {
 session.logout();
 }
 }
   
   }   

    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}

}

Option3 - Apache RedirectMap with Vanity/redirects Management UI in AEM

Define a redirect mapping XML file in AEM
<rules>
 <rule>
 <siteName></siteName>
 <code></code>
 <sourcePath></sourcePath>
 <target></target>

 </rule>
<rules>

  • Build a UI application to manage the redirects in the XML file
  • Validate the rules before adding to XML for accepted characters.
  • Replicate the mapping XML to a publisher on every modification through replication API
  • Configure the RedirectMap in apache - Separate map for 301 and 302 redirect
  • Build a Servlet in AEM that provides the txt file with redirect details based on the redirect type(301 or 302) from XML rule file

         #Source Target
         test test1
         test2  test3
         test4        test5 

  • Create a Cron job(script) in dispatcher that will get the redirect file(txt) file from AEM through servlet – separate request for 301 and 302 types of redirects every one hour and format it to Apache RedirectMap dbm format. The redirect map is reloaded with new redirects

Apache server handles the redirect so the load on AEM publisher will be reduced.

Sample Rule file:
<rules>
    <rule>
        <sitename>test</sitename>
        <type>302</type>
        <target>http://localhost:8080/content/geometrixx-outdoors/en.html</target>
        <source>/testVanity</source>
    </rule>
    <rule>
        <sitename>test</sitename>
        <type>302</type>
        <target>http://localhost:8080/content/geometrixx-outdoors/en/men.html</target>
        <source>/testVanity1</source>
    </rule>
</rules>

Cron Job to fetch the redirects: Add the following script into /etc/cron.hourly:

#!/bin/bash
 wget http://localhost:4503/services/getRedirectMapping.html -O /tmp/redirectmap.txt >> /var/log/update-redirect-map.log 2>&1
 httxt2dbm -i /tmp/redirectmap.txt -o /etc/httpd/conf/redirectmap.map >> /var/log/update-redirect-map.log 2>&1

Redirect Map configuration in Virtualhost:
RewriteMap redirects "dbm=db:C:\Albin\SW\redirectmap.txt"
RewriteCond ${redirects:$1} !=""
RewriteRule ^(.*)$ ${redirects:$1} [redirect=permanent,last]

Sample GetRedirectMapping Servlet:
import java.io.IOException;
import java.io.PrintWriter;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Session;
import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

@SuppressWarnings("serial")
@SlingServlet(paths= "/services/getRedirectMapping")
public class GetRedirectMapping extends SlingSafeMethodsServlet {

 @Reference
 private ResourceResolverFactory resolverFactory;

 @Override
 protected void doGet(final SlingHttpServletRequest req, final SlingHttpServletResponse resp)
 throws ServletException, IOException {

 String redirectMapPath="/content/geometrixx-outdoors/en/redirectmap.xml/jcr:content";
 Session session = null;

  resp.setContentType("text/html");
  PrintWriter pw = resp.getWriter();

 // this is for demo purpose
     //the redirect should be parsed and stored in HashMap and updated on every modification
      //Servlet should list the redirects from HashMap

 try {

  ResourceResolver resourceResolver = resolverFactory.getAdministrativeResourceResolver(null);
       session=resourceResolver.adaptTo(Session.class);
      // create a new DocumentBuilderFactory
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                  
          if(session.nodeExists(redirectMapPath)) {
              Node node = session.getNode(redirectMapPath);
              if(node.hasProperty("jcr:data")) {
                  Property jcrData = node.getProperty("jcr:data");
                  DocumentBuilder builder = factory.newDocumentBuilder();           
                  InputSource is = new InputSource(jcrData.getBinary().getStream());
                  Document doc = builder.parse(is);
                  NodeList rules=doc.getElementsByTagName("rule");
                  for (int i = 0; i < rules.getLength(); i++) {
                   Element rule = (Element) rules.item(i);                 
                   NodeList sourceElements = rule.getElementsByTagName("source");
                      Element source = (Element) sourceElements.item(0);
                      NodeList sourceChildren = source.getChildNodes();
                      String sourceValue = sourceChildren.item(0).getNodeValue(); 
                   
                      NodeList targetElements = rule.getElementsByTagName("target");
                      Element target = (Element) targetElements.item(0);
                      NodeList targetChildren = target.getChildNodes();
                      String targetValue = targetChildren.item(0).getNodeValue();                 
                                     
                      pw.println(sourceValue+" "+targetValue );
   
                  }
              }
          }

 } catch (Exception e) {
 // TODO Auto-generated catch block

 e.printStackTrace();
 }finally {
 if(session!=null)
 {
 session.logout();
 }
 }
 }

}

Option4: ACS Redirect Map Manager
Refer the following URL for more details https://adobe-consulting-services.github.io/acs-aem-commons/features/redirect-map-manager/index.html

Apache server handles the redirect so the load on AEM publisher will be reduced.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.