Friday 19 August 2016

Hidden Power of ATG | Service Cache

As we all know the importance of cache in web application performance. In the case of ATG most of the developers  are aware of repository and cache droplet.


While working on one of the Oracle commerce application. I came across new cache from ATG (called service cache). 

Below are the limitations of the above mentioned caches [repository/droplet].

1. Cache Droplet : Accessible through JSP pages. This caches the generated html fragments which includes whites space, this results in increased cache size [comparatively big cache].
2. Repository Cache : Applicable when accessing Database through repository layer.

This cache [Service Cache] is useful in number of scenarios like when you want to access database directly but still want to cache the results. You can also cache data like from other systems like third party response of some call or result of Endeca presentation API.

Here I am going to explain to configure this cache for caching the results in droplet call.

1. Create Cache Key Class : This class will represent the cache key object. Optionally you can directly create the string cache key in droplet itself. But this approach is useful when cache key is complex [includes language/local/segments]. This is also helpful to easily extend the cache key in future [add more fields]. Here you need to override the equals and hashCode methods [Internally used to compare cache keys].

=========================================================================
public class DropletCacheKey {

private String mLanguage;
private String mSegment;
private String mKey; 

public String getLanguage() {
        return mLanguage;
    }

public void setLanguage(String pLanguage) {
        this.mLanguage = pLanguage;
    }

public String getSegment() {
        return mSegment;
    }

public void setSegment(String pSegment) {
        this.mSegment = pSegment;
    }    

 public String getKey() {
        return mKey;
    }

public void setKey(String pKey) {
        this.mKey = pKey;
    }

 private boolean isSameRecordKey(DropletCacheKey pObject) {
        return (this.mKey == null && pObject.getKey() == null) ||
        (this.mKey!= null && pObject.getKey()!= null && pObject.getKey().equals(this.mKey)); 
    }

private boolean isSameRecordLanguage(DropletCacheKey pObject) {
        return (this.mLanguage == null && pObject.getLanguage() == null) ||
                  (this.mLanguage!= null && pObject.getLanguage()!= null
                  && pObject.getLanguage().equals(this.mLanguage)); 
    }

private boolean isSameRecordSegment(DropletCacheKey pObject) {
        return (this.mSegment == null && pObject.getSegment() == null) ||
                  (this.mSegment!= null && pObject.getSegment()!= null 
                 &&   pObject.getSegment().equals(this.mSegment)); 
    }

@Override
    public boolean equals(Object pObject){
        if(pObject instanceof
DropletCacheKey){
           
DropletCacheKey cacheKey = (DropletCacheKey) pObject;
            return (isSameRecordKey(
cacheKey)
                      && isSameRecordLanguage(cacheKey) && isSameRecordSegment(cacheKey));
        }
        return false;
    }

@Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("KEY : [ ");
        builder.append(mKey).append("|");
        builder.append(mLanguage).append("|");
        builder.append(mSegment);
        builder.append(" ]");
        return builder.toString();
    }

@Override
public int hashCode() {
        int PRIME = 31;
        int result = 1;
        result = PRIME * result + ((StringUtils.isBlank(mKey)) ? 0 : mKey.hashCode());
        result = PRIME * result + ((StringUtils.isBlank(mLanguage)) ? 0 : mLanguage.hashCode());
        result = PRIME * result + ((StringUtils.isBlank(mSegment)) ? 0 : mSegment.hashCode());
        return result;
    }
}
=========================================================================

2. Create Cache Adapter Class : This class contains the business logic to fetch actual result.

=========================================================================
public class DropletCacheAdapter extends GenericService implements CacheAdapter {

@Override
public Object getCacheElement(Object pKey) throws Exception {
     if (pKey != null) {
        // Write business logic here to fetch actual result and return the same
     }
}

@Override
    public int getCacheElementSize(Object pArg0, Object pArg1) {
        return 0;
    }

@Override
    public Object[] getCacheElements(Object[] pKeys) throws Exception {
        vlogDebug("Entering Object[] getCacheElements(Object[] pKeys) : " );
        if (pKeys != null) {
            int keyLength = pKeys.length;
            vlogDebug("Key Length :: " + keyLength);
            Object elements[] = new Object[keyLength];
            for (int i = 0; i < keyLength; i++) {
                elements[i] = getCacheElement(pKeys[i]);
            }
            return elements;
        } else {
            return null;
        }
    }

@Override
    public int getCacheKeySize(Object pArg0) {
        return 0;
    }

@Override
    public void removeCacheElement(Object pArg0, Object pArg1) {
    }
}
========================================================================= 
3. Create Cache Adapter Component : Configure cache adapter component using above class.

=========================================================================
# /com/cache/DropletCacheAdapter
 $class=com.cache.DropletCacheAdapter 
 $scope=global
cache=/atg/service/cache/Cache
=========================================================================
 
4. Create Cache Component : Now its time to configure cache component

=========================================================================
 # /com/cache/DropletCache
$class=atg.service.cache.Cache
$scope=global

# injecting caching adapter
cacheAdapter=
/com/cache/DropletCacheAdapter
#Configure below values as per your application need
maximumCacheEntries=10000
maximumCacheSize=10485760
maximumEntryLifetime=43200000
=========================================================================
 
5. Inject Cache component as property in droplet component : This is required as we need to refer cache inside the droplet code.

=========================================================================
 $class=com.search.droplet.StoreDroplet
$scope=global
dropletCache=
/com/cache/DropletCache
#inject other required component
=========================================================================

6. Update Droplet code to use this cache : Here first of all you need to create cachekey object based on input parameter [in this case language, segment and key]. Then invoke the get method on cache component [injected in above step].

=========================================================================
 public class StoreDroplet extends DynamoServlet {    

    private Cache  mDropletCache;

    public Cache
getDropletCache() {
        return
mDropletCache;
    }

   public void
setDropletCache(Cache pDropletCache) {
        this.
mDropletCache= pDropletCache;
    }

public void service(DynamoHttpServletRequest pRequest, DynamoHttpServletResponse pResponse) throws ServletException, IOException { 

// Create cache key using custom method here you need to write method that will return populated object
// of type DropletCacheKey you can modify definition of this object [class] as per your need.

// next fetch the result from cache using this key. This will automatically take care of adding object to cache 
// if already not done. And fetch object from cache if it is available there.
// Here you can also type cast the result. This type cast must be same as retuned type object of //getCacheElement method of cache adapter.
getDropletCache.get(dropletCacheKey);

//further you can utilize the retuned object 

}

}
=========================================================================

Note : You can monitor this cache from dyn/admin.

Monday 15 August 2016

ATG request pipeline servlets vs J2EE servlets

Below are two major differences between ATG Request pipeline servlets and J2EE servlets.

1. ATG servlets exist in the servlet pipeline, which executes before the request reaches the J2EE web container. J2EE servlets are executed by the web container.

2. ATG servlets themselves determine the order in which they execute. The application deployment descriptor web.xml describes the order and conditions in which J2EE servlets execute.

Wednesday 10 August 2016

ATG | Configure request handling pipeline servlets [Customize request handling pipeline]

Below are the two common ways to configure [create] request handling pipeline servlets in ATG.

1. Extend atg.servlet.pipeline.PipelineableServletImpl.
2. Extend atg.servlet.pipeline.InsertableServletImpl.

1. Extend atg.servlet.pipeline.PipelineableServletImpl 

Follow below steps to configure servlet using PipelineableServletImpl.
  • Extend atg.servlet.pipeline.PipelineableServletImpl.
  • Define the servlet as a globally scoped Nucleus component.
  • Reset the previous servlet’s nextServlet property to point to the new servlet.
  • Set the new servlet’s nextServlet property to point to the next servlet in the pipeline.
  • Add the servlet’s path to the initialServices property of /atg/dynamo/servlet/Initial. 

 2. Extend atg.servlet.pipeline.InsertableServletImpl

Follow below steps to configure servlet using InsertableServletImpl.
  • Extend atg.servlet.pipeline.InsertableServletImpl.
  • Define the servlet as a globally scoped Nucleus component.
  • Set the insertAfterServlet property of your servlet to point to the path of the pipeline servlet you want your servlet to follow. 
    For example, you can insert a servlet after StoreServlet as follows :
    insertAfterServlet=/atg/dynamo/servlet/dafpipeline/StoreServlet
  • Add the servlet’s path to the initialServices property of /atg/dynamo/servlet/Initial
Advantage of second approach is you no need to make any change in the existing servlets.