WC Sites Groovy Wrapper CSElement Example

Introduction

Akamai enables one to not only cache pages based upon the URI requested (e.g. /en/download/index.jsp) but also with a flexible cache key. This permits many variations of a single URI to be cached by Akamai based upon some value. Effective caching of content thereby offloading serving content to Akamai is critical for Oracle – without it they cannot run a high-performance website that responds quickly for a distributed global user base.

To complicate caching; some pages on www.java.com (JCOM) render differently based upon the visitors “user-agent” header. For example a visitor on Windows must see a different download page than a visitor on Mac OS X (for the same URI).

By joining the URI of the page and the value from the ORA_FLEX_CACHE_KEY cookie we can create a flexible cache key that Akamai can use. Akamai can then cache many versions of the same URI thereby offloading traffic to Akamai rather than having it coming back to Oracle’s servers.

Example:

  • URI = /en/download/index.jsp
  • ORA_FLEX_CACHE_KEY = windows-chrome
  • Akamai cache key = /en/download/index.jspwindows-chrome

Any visitor that then requests this URI who is on Windows and Chrome (regardless of if they are on XP, Win 7, Win 8 or Chrome 24, 25, 26…) will get the same version sent back to them from Akamai. As such, a simple wrapper was needed to be implemented on WC Sites that enabled this key feature.

Main Article

The team that is implementing www.java.com (JCOM) are implementing a custom groovy wrapper element that deals with determining the proper user-agent/cookie and consequently, allows the webpage Template to properly render the correct content for each visitor on each page. The current POC code can be seen on lines 22-70 as shown in the code snippet below. Within a few weeks they expect to have that logic pushed down into a separate CSElement (for easier re-use) and further, they won’t be manually grepping the user-agent. Instead they’ll be leveraging BrowserHawk a 3rd party licensed library that handles all current and future user-agent combinations. When they update the code, I’ll post revisions here. In the meantime, here is the current POC code:

import COM.FutureTense.Interfaces.ICS
import COM.FutureTense.Interfaces.FTValList
import COM.FutureTense.Interfaces.Utilities;
import COM.FutureTense.Util.ftMessage
import COM.FutureTense.Util.ftErrors
import com.fatwire.system.Session
import com.fatwire.system.SessionFactory
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

String queryString = ics.getIServlet().getQueryString();

// Get a general logger for this dispatcher ...
String generalLogName = "com.oracle.pdit.jcom.groovy.jcomgroovydispatcher.general";
Log logGeneral = LogFactory.getLog(generalLogName);
logGeneral.debug("BEGIN: jcom-groovy-dispatcher.groovy [" + queryString + "]");

// test if this is a detection request from Akamai
if(queryString.contains("requesttype=detect")){

	String akamaiLogName = "com.oracle.pdit.jcom.groovy.jcomgroovydispatcher.akamai";
	Log logAkamai = LogFactory.getLog(akamaiLogName);

	String userAgent = ics.getIServlet().getServletRequest().getHeader("User-Agent").toLowerCase();
	ArrayList<String> tokens = new ArrayList<String>(); // used to store the various values before we build and set the cookie

	// Browser checks...
	if(userAgent.contains("firefox")){
		tokens.add("firefox");
	}else if(userAgent.contains("chrome")){
		tokens.add("chrome");
	}else if(userAgent.contains("msie")){
		tokens.add("ie");
	}else if(userAgent.contains("opera")){
		tokens.add("opera");
	}else if(userAgent.contains("safari")){
		tokens.add("safari");
	}

	// OS Checks
	if(userAgent.contains("windows")){
		tokens.add("win");
	}else if(userAgent.contains("mac")){
		tokens.add("mac");
	}else if(userAgent.contains("linux")){
		tokens.add("linux");
	}

	// 32-bit vs 64-bit Checks
	if(userAgent.contains("wow64")){
		tokens.add("wow64");
	}else if(userAgent.contains("win64")){
		tokens.add("win64");
	}

	// build a cookie value
	String cookieVal = "desktop";
	for (String token : tokens){
	    cookieVal += "-" + token;
	}
	// but if we couldn't detect any values then just set it to empty string
        if (tokens.size() == 0){
		cookieVal = "";
	}

	// set the cookie
	logAkamai.debug("Setting ORA_FLEX_CACHE_KEY cookie: [" + cookieVal + "], for User-Agent: [" + userAgent + "]");
	ics.SetCookie("ORA_FLEX_CACHE_KEY", cookieVal, 3600*24*30*12, "/", "java.com " , false);

}else{ // render the page as normal with layout template

	String callTemplateLogName = "com.oracle.pdit.jcom.groovy.jcomgroovydispatcher.calltemplate";
	Log logCallTemplate = LogFactory.getLog(callTemplateLogName);

	// determine template name
	String tName = "";
	if(ics.GetVar("c")==null || ics.GetVar("site")==null){
		tName = ics.GetVar("childpagename");
	}else{
		tName = ics.GetVar("childpagename").replaceAll(ics.GetVar("site") + "/" + ics.GetVar("c") + "/", "");
	}

	FTValList updateVal = new FTValList();
	FTValList valueForCallTemplate = new FTValList();
	valueForCallTemplate.setValString("SITE",ics.GetVar("site"));
	valueForCallTemplate.setValString("SLOTNAME","JCOMPage");
	valueForCallTemplate.setValString("TID",ics.GetVar("eid"));
	valueForCallTemplate.setValString("TNAME",tName);
	valueForCallTemplate.setValString("C",ics.GetVar("c"));
	valueForCallTemplate.setValString("CID",ics.GetVar("cid"));
	valueForCallTemplate.setValString("TTYPE","CSElement");
	valueForCallTemplate.setValString("PACKARGS",ics.GetVar("argslist"));
	valueForCallTemplate.setValString("locale",ics.GetVar("locale"));
	logCallTemplate.debug("Calling template: [" + tName + "], for [c:" + ics.GetVar("c") + ", cid:" + ics.GetVar("cid") + ", site:" + ics.GetVar("site") + "]");
	final String s = ics.runTag("RENDER.CALLTEMPLATE", valueForCallTemplate);
	if (s != null){
	   ics.StreamText(s);
	}

}

logGeneral.debug("END: jcom-groovy-dispatcher.groovy [" + queryString + "]");

Note: Dolf Dijkstra from the A-Team helped them with the code regarding how to call a template on lines 75-101.

To complete the solution, in front of this groovy CSElement the JCOM team simply have an uncached SiteEntry set as a wrapper (see attached screenshot).

ScreenCapture Groovy wrapper

 

In summary, this is a nice, flexible pattern that can be adapted to many other projects!

Addendum:

BrowserHawk Overview

BrowserHawk is a 3rd party product licensed from Cyscape. It is used to detect a visitors environment characteristics such as OS and Browser.

BrowserHawk Product Documentation

 

Add Your Comment