Pages

Sunday, April 1, 2018

Different approaches to perform Vanity/Redirect URL management in Adobe Experience Manager(AEM)

Different approach to perform Vanity/Redirect URL management in Adobe Experience Manager(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 approach to manage the Vanity/Redirect URL through centralized management UI.

Option1: Manage the Vanity URL's through VanityPath page property

  • Build a UI application to read vanityPath properties from the repository content nodes and enable management options - Add, Delete, Modification
  • Refer the following URL for details on this approach - https://helpx.adobe.com/experience-manager/using/vanitypath.html(only working in 6.1)
  • Whitelist vanity paths in dispatcher - https://helpx.adobe.com/experience-manager/dispatcher/using/dispatcher-configuration.html#EnablingAccesstoVanityURLsvanityurls
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


VanityURL_Redirect_Java_Filter


Store the vanity and redirects mapping in a XML file with in 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 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



VanityURL_RedirectMap

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 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.







No comments:

Post a Comment