Sunday 25 December 2016

Endeca | Add a new site to an existing application

You can easily add new site to existing endeca application by following below steps.

Nature

1. Export the application you want to modify (Add a site).
   a. Navigate to application control directory.
   b. Invoke this command to export application.
        runcommand.<bat|sh> IFCR exportApplication <destination directory>
   c.  Navigate to destination directory and unzip the exported zip file.

2. Add the default site ID, site node and site definition for you new site.
  a. Navigate to the pages node(directory) of your unzipped exported application. 
  b. Open the _.json file in an editor and add a default site ID.
      For example:
      {
         "defaultSiteId": "/TestStoreDE",
         "ecr:type": "page-root"
      }
  c) Navigate to the <export directory>\pages directory. Note that the application already has an
      existing site. For example, the Store application has an existing site with TestStoreUS as the
      site_ID.
  d) Create a folder for your new site parallel to the existing site. The name that you provide for the folder
       becomes the site_ID, for example
TestStoreDE.
  e) Navigate to the existing site in your application, for example pages\
TestStoreUS.
  f) Copy the site definition file, _.json, from the folder and paste it into your new site folder, for example
      pages\
TestStoreDE\_.json.
  g) Use a text editor to update the site definition with unique information appropriate for your new site. For example, the following new site has been updated with a unique URL pattern, display name, and
      description.
      {
          "ecr:type": "site-home",
          "urlPattern" : "/
TestStoreDE",
          "displayName" : "German Store",
          "description" : "This is German Store",
      }

  h) If you want the site to have its own unique icon to identify it in Experience Manager, you can replace
      the default site icon with one of your own by copying an image to the same directory as the site's
      JSON file.The image must be 16 by 16 pixels, and be named siteIcon.<extension>, for example
      pages\TestStoreDE\siteIcon.png. The supported formats are JPG, JPEG, PNG, GIF, and TIF.         Oracle recommends using the PNG format.
  i) Save and close the file.

3. If you want your site to filter a subset of relevant records, you need to add a site-based filter to your site.
    a) Navigate to the new site directory pages/<new site_ID>, for example pages/TestStoreDE.
    b) Create an XML file and name it filterState.xml. The following file filters records so that only those
         records in the furniture category can appear in the site.
          <Item class="com.endeca.infront.navigation.model.FilterState"
                     xmlns="http://endeca.com/schema/xavia/2010">
                   <Property name="recordFilters">
                     <List>
                          <String>product.category:furniture</String>
                     </List>
                   </Property>
          </Item>
   c) Save and close the file.
4. A site needs at least one page in order to display so copy and paste pages that you want from your original site to your new site.
For example, you could copy the browse or account folder from pages/TestStoreUS to pages/TestStoreDE.
5. Import the content with the new site information.
   a. Navigate to application control directory.
   b. Invoke this command to import updated application.
       runcommand.<bat/sh> IFCR importContent pages <path to source>.

Saturday 24 December 2016

Endeca Baseline Error on Linux VM | Can't locate strict.pm in @INC

While running baseline on new Endeca application got below error.

Error : Can't locate strict.pm in @INC (@INC contains <path to some perl directories>)

Can't Locate

Solution :

1. Run below commands.

export PERLLIB=$ENDECA_ROOT/lib/perl:$ENDECA_ROOT/lib/perl/Control:$ENDECA_ROOT/perl/lib:$ENDECA_ROOT/perl/lib/site_perl
export PERL5LIB=$ENDECA_ROOT/lib/perl:$ENDECA_ROOT/lib/perl/Control:$ENDECA_ROOT/perl/lib:$ENDECA_ROOT/perl/lib/site_perl

2. Restart PlatformServices and trigger indexing again.

Note : Set ENDECA_ROOT properly before running this command.

Wednesday 7 December 2016

ATG 11.2 BCC Full Deployment ConcurrentUpdateException

While running full deployment on ATG 11.2 first time it failed due to below exception.

============================================================================
2016-12-06 19:25:53,345 ERROR [STDERR] (RepositoryWorkerThread-1(Add-Update Phase)) atg.repository.ConcurrentUpdateException: no rows updated oldVersion=36 for item=deploymentProgress:1100003 in GSATransaction=atg.adapter.gsa.GSATransaction@1890638 thread=RepositoryWorkerThread-1(Add-Update Phase) transaction=TransactionImple < ac, BasicAction: aaf04af:134f:4e650790:b4cf status: ActionStatus.RUNNING >
============================================================================

Solution : Run full deployment with DeploymentManager.maxThreads=1 configuration.

Step 1 : Open the dyn/admin of BCC instance.
Step 2 : Open DeploymentManager component.
Step 3 : Update maxThreads property to 1.
Step 4 : Run full deployment again.

Saturday 12 November 2016

MDEX Performance boost | Increase Number of threads

MDEX engine can be configured to run in multi threaded mode. By default MDEX runs in multi threaded mode only with number of threads configured to 1. Multi threaded mode cannot be disabled.

Engine 
How to configure [increase] number of threads : Number of threads are controlled with --threads flag. Specify the --threads flag when starting the MDEX Engine. Or you can mention same in MDEX configuration xml file. For example

 <arg>--threads</arg>
      <arg>2</arg>

 
Key benefits of multi threaded MDEX engine.

Large index files on disk : Single index files are shared between all the threads.
Long-running queries   : Some of thread responds to long running queries while others can respond to different queries.
Simplified system management and network architecture : Need to configure only single MDEX engine.
Applications with high throughput requirements with limited hardware resources : Fewer hard ware resources required than multiple single threaded MDEX engines.
Applications that heavily use the MDEX Engine dynamic cache : Threads in multi threaded MDEX shares the same dynamic cache.

Note : Recommended number of threads for the MDEX Engine is typically equal to the number
of cores on the MDEX Engine server.

Wednesday 12 October 2016

ATG | Persist price information for incomplete orders

By default ATG won't persists price information for incomplete orders. If you print incomplete order from dyn/admin, you won't get price information.

Incomplete Order without price information :
========================================================================= 
------ Printing item with id: o120001 
<add-item item-descriptor="order" id="o120001"> 
<set-property name="description"><![CDATA[o120001]]></set-property> 
<set-property name="relationships"><![CDATA[r30001]]></set-property> 
<set-property name="commerceItems"><![CDATA[ci3000001]]></set-property
<set-property name="shippingGroups"><![CDATA[sg120001]]></set-property>
<set-property name="profileId"><![CDATA[260000]]></set-property> 
<set-property name="siteId"><![CDATA[homeSite]]></set-property> 
<set-property name="orderClassType"><![CDATA[default]]></set-property>
<set-property name="creationSiteId"><![CDATA[homeSite]]></set-property>
<set-property name="creationDate"><![CDATA[10/12/2016 22:50:01]]></set-property>
<set-property name="paymentGroups"><![CDATA[pg120001]]></set-property>
</add-item>
=========================================================================

To achieve this update orderStateSaveModes (Order state to save mode mapping) property of /atg/commerce/order/processor/SavePriceInfoObjects/ processor [add mapping for INCOMPLETE order].

Example Mapping :

INCOMPLETE=ALL

The valid save modes are :
  • ALL : Saves all PriceInfo types.
  • ALL_NO_AUDIT : Saves all PriceInfo types, but does not save audit trail information (pricing adjustments and detailed price info objects).
  • ORDER : Saves only the OrderPriceInfo object (not shipping, item, tax).
  • ORDER_NO_AUDIT : Saves only the OrderPriceInfo object, with no audit information.
  • NONE : Saves no pricing information.
ProcSavePriceInfoObjects also includes a defaultSaveMode to use if the current order state does not have an entry in the orderStateSaveModes map.

Incomplete Order with price information (post above configuration change) :
=========================================================================
------ Printing item with id: o120001 
<add-item item-descriptor="order" id="o120001"> 
<set-property name="description"><![CDATA[o120001]]></set-property> 
<set-property name="relationships"><![CDATA[r30001]]></set-property> 
<set-property name="commerceItems"><![CDATA[ci3000001]]></set-property>
<set-property name="shippingGroups"><![CDATA[sg120001]]></set-property>
<set-property name="profileId"><![CDATA[260000]]></set-property>  
<set-property name="siteId"><![CDATA[homeSite]]></set-property> 
<set-property name="orderClassType"><![CDATA[default]]></set-property> 
<set-property name="creationSiteId"><![CDATA[homeSite]]></set-property> 
<set-property name="taxPriceInfo"><![CDATA[ai10005]]></set-property>
<set-property name="creationDate"><![CDATA[10/12/2016 22:50:01]]></set-property>
<set-property name="priceInfo"><![CDATA[ai10001]]></set-property> 
<set-property name="paymentGroups"><![CDATA[pg120001]]></set-property> 
</add-item>
=========================================================================

ATG 11.1 CRS | SEVERE: Unable to retrieve sites - unable to locate root node

After starting weblogic server, tried to hit CRS home page. But got the blank page.

Below is the exception from logs.

==========================================================================
Oct 12, 2016 9:46:58 PM com.endeca.infront.site.SiteManager getSite
SEVERE: Unable to retrieve sites - unable to locate root node
**** Error      Wed Oct 12 21:47:00 IST 2016    1476289020935   /atg/endeca/assembler/droplet/InvokeAssembler   A problem occurred assembling the content for content item /content/Web/Home Pages. The response received was {previewModuleUrl=http://localhost:8006/ifcr, @type=ContentSlot, atg:currentSiteProductionURL=/crs/homeus, canonicalLink=com.endeca.infront.cartridge.model.NavigationAction@2a2248a2, ruleLimit=1, @error=com.endeca.infront.content.ContentException: com.endeca.navigation.ENEConnectionException: Error establishing connection to retrieve Navigation Engine request 'http://localhost:15000/graph?node=0&profiles=site.homeSite|sitegroup.siteGroupUS|NoPriceRange&offset=0&nbins=0&merchdebug=1&irversion=640'. Tried all: '1' addresses, but could not connect over HTTP to server: 'localhost', port: '15000' Check MDEX Logs and specified query parameters. , contentCollection=/content/Web/Home Pages}. Servicing the error open parameter.
==========================================================================

Solution : Start\re-start MDEX Engine.

Sunday 2 October 2016

ATG | Configure Last-Modified Property for repository items

Sometimes we need to persist the information, when a repository item was most recently modified. In ATG there is a way to configure last-modified property for item descriptor. 



Follow below steps to configure last-modified property for a given item-descriptor.

1. The item descriptor must contain a date or timestamp property that stores the last-modified value. This property must be persistent (add column in one of the existing table) and single-valued.

   <property name="lastActivity" data-type="timestamp"/>

2. The item descriptor sets the last-modified-property attribute to the name of the last-modified property (defined in above step).

    <item-descriptor name="customerServiceArticle" last-modified-property="lastActivity">

3. The item descriptor sets the updateLastModified <attribute> (attribute of item descriptor) element to true.

<attribute name="updateLastModified" value="true"/>

Example item descriptor (Only demonstrating last modified property).
==========================================================================
<item-descriptor name="customerServiceArticle" last-modified-property="lastActivity">

    <attribute name="updateLastModified" value="true"/>

    <table name="customer_articles" type="primary">
        <property name="lastActivity" data-type="timestamp"  column-name="last_updated"/>
    </table>

</item-descriptor>
 =========================================================================

Note : This property is automatically (no code required) updated with the current time when the item is added or updated.

Friday 30 September 2016

ATG Endeca Integration | Write log [Logging API] requests while using Assembler API

In the case we want to use endeca logging and reporting. The first thing is we need to write log entries in LogServer's log file. Here I am going to explain, how to write log request to log file with assembler API.


Below are steps : 

1. Create class which extends RequestEventListener.
2. Create global scoped component of this class.
3. Add this component in assemblerEventListeners property of NucleusAssemblerFactory.

Below is the example : 

1. Create class which extends RequestEventListener.

=========================================================================
package com.shop.endeca.assembler.logserver;

import atg.servlet.ServletUtil;
import com.endeca.infront.assembler.ContentItem;
import com.endeca.infront.assembler.event.request.RequestEvent;
import com.endeca.infront.assembler.event.request.RequestEventListener;
import com.endeca.infront.navigation.event.NavigationEventWrapper;
import com.endeca.logging.*;
import atg.nucleus.logging.ApplicationLogging;
import atg.nucleus.logging.ClassLoggingFactory;


public class LogServerAdapter extends RequestEventListener
{

    private static ApplicationLogging mLogger =     ClassLoggingFactory.getFactory().getLoggerForClass(LogServerAdapter.class);
   
    private static LogConnection Logconn;

    private LogConnection getLogconn() {
        Logconn=new LogConnection(getLogServer(),getPortNumber());
        return Logconn;
    }

    public void setLogconn(LogConnection conn) {
        this.Logconn =Logconn;
    }
   
    // Endeca server hostname where endeca log server is hosted.
    private String logServer;

    public  String getLogServer() {
        return logServer;
    }

    public void setLogServer(String logServer) {
        this.logServer = logServer;
    }

    // Endeca Log Sever port number
    private Integer portNumber;


    public Integer getPortNumber() {
        return portNumber;
    }

    public  void  setPortNumber(Integer portNumber) {
        this.portNumber = portNumber;
    }

    /**
     * @return ApplicationLogging object for logger.
     */
    private ApplicationLogging getLogger() {
        return mLogger;
    }


    @Override
    public void handleAssemblerRequestEvent(RequestEvent assemblerReqEnt, ContentItem conItemPar) {
       
        try
        {
            LogEntry handleLog = new LogEntry();
            Logconn=getLogconn();
           
           NavigationEventWrapper eventNavigation = new NavigationEventWrapper(assemblerReqEnt);
            if (assemblerReqEnt.getSessionId() != null) {
                handleLog.putString("SESSION_ID", assemblerReqEnt.getSessionId());

            }


            if (eventNavigation.getAutocorrectTo() != null) {
                handleLog.putString("AUTOCORRECT_TO", eventNavigation.getAutocorrectTo());
            }


            if (eventNavigation.getDimensions() != null) {
                handleLog.putList("DIMS", eventNavigation.getDimensions());
            }


            if (eventNavigation.getDimensionValues() != null) {
                handleLog.putList("DVALS", eventNavigation.getDimensionValues());
            }


            if (eventNavigation.getEneTime() != null) {
                handleLog.put("ENE_TIME", eventNavigation.getEneTime());
            }


            if (eventNavigation.getNumRecords() != null) {
                handleLog.put("NUM_RECORDS", eventNavigation.getNumRecords());
            }

            if (eventNavigation.getRecordNames() != null) {
                handleLog.putList("RECORD_NAMES", eventNavigation.getRecordNames());
            }


            if (eventNavigation.getRequestType() != null) {
                handleLog.putString("TYPE", eventNavigation.getRequestType().toString());
            }


            if (eventNavigation.getSearchKey() != null) {
                handleLog.putString("SEARCH_KEY", eventNavigation.getSearchKey());
            }

            if (eventNavigation.getNumRefinements() != null) {
                handleLog.put("NUMREFINEMENTS", eventNavigation.getNumRefinements());
            }


            if (eventNavigation.getSortKeys() != null) {
                handleLog.putList("SORT_KEY", eventNavigation.getSortKeys());
            }


            if (eventNavigation.getSpotlights() != null) {
                handleLog.putList("MERCH_RULES", eventNavigation.getSpotlights());
            }


            if (eventNavigation.getSearchMode() != null) {
                handleLog.putString("SEARCH_MODE", eventNavigation.getSearchMode());
            }


            if (eventNavigation.getSearchTerms() != null) {
                handleLog.putString("SEARCH_TERMS", eventNavigation.getSearchTerms());
            }


            Logconn.logAsynchronously(handleLog);


            if(getLogger().isLoggingDebug())
            {
                getLogger().logDebug("reporting log is updated.");
            }

        }
        catch (LogException e)
        {
            if (getLogger().isLoggingError())
            {
                getLogger().logError("There is some LogException"+e.getMessage());
            }
        }catch(Exception e)
        {
            if (getLogger().isLoggingError())
            {
                getLogger().logError("There is some other exception"+e.getMessage());
            }
           
        }
    }
}

=========================================================================

2. Create global scoped component of this class.

=========================================================================
 # /atg/endeca/assembler/logserver/LogServerAdapter
$class=com.shop.endeca.assembler.logserver.LogServerAdapter

$scope=global
portNumber=16010
logServer=localhost

=========================================================================

3. Add this component in assemblerEventListeners property of NucleusAssemblerFactory.

=========================================================================
 assemblerEventListeners+=\
    /atg/endeca/assembler/logserver/LogServerAdapter

=========================================================================

Wednesday 14 September 2016

Resolved | Workbench Error org.apache.sling.api.SlingException

Today while accessing Oracle Commerce Workbench got below exception on browser.
=========================================================================
The requested URL /ifcr/sites/Store.html resulted in an error in /apps/endeca/site/html.jsp. Exception: org.apache.sling.api.SlingException: at org.apache.sling.scripting.jsp.jasper.servlet.JspServletWrapper.handleJspExceptionIntern al (JspServletWrapper.java:560) atorg.apache.sling.scripting.jsp.jasper.servlet.JspServletWrapper.handleJspException (JspServletWrapper.java:496)
=========================================================================

Solution : Restart the Endeca ToolsAndFrameworks

Wednesday 7 September 2016

ATG BCC | Disable(hide) Promotion Template in template selection screen

Sometimes business is not utilizing all the available promotion templates in BCC. So it is better to hide these non-required templates in template selection screen in BCC.

Here are the steps : 


1. Copy pmdt file for promotion to disable in \atg\registry\data\promotiontemplates\item directory of "Versioned" module of your application.You can find existing templates at DCS\config\config.jar\config\atg\registry\atg\registry\data\promotiontemplates.

2. Add available-in-ui="false" attribute in ui-description tag of pmdt.

3. Rebuild and Redeploy the BCC ear. Now this template won't be available in template selection screen.

In below pmdt  SpendY GetGiftWithPurchase promotion is disabled.

==========================================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE template PUBLIC "-//Art Technology Group, Inc.//DTD PMDT 1.0//EN" "http://www.atg.com/dtds/pmdt/pmdt_1.0.dtd">
<template item-type="Item Discount"
                    author="Sean McIntyre"
                    last-modified-by="Sean McIntyre"
                    creation-date="11/02/2011">
  <ui-description display-name-resource="template.gwp.spendYGetGiftWithPurchase.title" 

             resource-bundle="atg.remote.promotion.template.Resources" available-in-ui="false">
  </ui-description>
 </template>

==========================================================================

Sunday 4 September 2016

ATG | Internationalizing the repository items

Here I am going to explain internationalization "best-practice" strategy provided by ATG.

Here are the steps :

1. Identify properties for translation in given item-descriptor. In below example title,content, location.

2. Now create new properties in base item-descriptor [article]. These properties will point to existing properties database column. For example we have created contentDefault property to refer long_description column. This column was initially referred by content property. Name these properties in such a way so that these can bee easily differentiate from derived property [postfix property name with Default].

3. Create the helper item descriptor to store the translated data. This item-descriptor will contain all the translated properties from main item-descriptor. Here articleTranslation used for this purpose.

4. Now add map type property in base item-descriptor which is map of locale to translated item [defined in above step]. In below example translations property is used to map locale to articleTranslation.

5. Change the definitions of the translatable properties in the existing item types. The new definitions should specify that each translatable property is a derived property. Here atg.repository.dp.LanguageTranslation class used to implement the derivation.

Below mechanism is used to find the values of translatable property.
  •  Use the current locale to look up a corresponding baseTypeTranslation [articleTranslation] item in the translations property map. The property derivation attempts to find a best match. First, it searches the locale keys for a match on the entire locale with a variant, then it searches for a match on the locale without a variant, and finally it searches on just the language code.
  •  If a baseTypeTranslation [articleTranslation] item exists for the current locale, use its value for the property.
  • If a baseTypeTranslation [articleTranslation] item does not exist for the current locale, or its value for the property is null, use the translatablePropertyDefault [stored in base item-descriptor] value instead.
Below is the example of internationalization : Here we have article repository item [with 3 properties title,content, location]. Here articleTranslation item-descriptor is used to translate these properties.
==========================================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<gsa-template>

    <!--///////////////////////////////-->
    <!--     article                         -->
    <!--///////////////////////////////-->
   
    <item-descriptor name="article" display-property="titleDefault" display-name="article">
   
        <table name="shop_article" type="primary" id-column-name="article_id">
            <property name="id" data-type="string" column-name="article_id"  display-name="article_id"/>
            <property name="titleDefault" data-type="string" column-name="title" 

                     display-name="titleDefault"/>   
            <property name="contentDefault" data-type="big string" column-name="long_description" 

                    display-name="contentDefault"/>
            <property name="locationDefault" data-type="string" column-name="article_location" 

                    display-name="location"/>   
        </table>
       
        <property name="title" data-type="string" writable="false" hidden="true" queryable="true" 

                    display-name="title">
            <derivation user-method="atg.repository.dp.LanguageTranslation">
                <expression>titleDefault</expression>
            </derivation>
            <attribute name="defaultProperty" value="titleDefault" />
            <attribute name="defaultLocale" value="en" />
        </property>
       
        <property name="content" data-type="big string" writable="false" hidden="true" queryable="true"
            display-name="content">
            <derivation user-method="atg.repository.dp.LanguageTranslation">
                <expression>contentDefault</expression>
            </derivation>
            <attribute name="defaultProperty" value="contentDefault" />
            <attribute name="defaultLocale" value="en" />
        </property>
       
        <property name="location" data-type="string" writable="false" hidden="true" queryable="true"
            display-name="location">
            <derivation user-method="atg.repository.dp.LanguageTranslation">
                <expression>locationDefault</expression>
            </derivation>
            <attribute name="defaultProperty" value="locationDefault" />
            <attribute name="defaultLocale" value="en" />
        </property>
       
        <table name="shop_article_translations" type="multi" multi-column-name="locale" 

                     id-column-names="article_id">
            <property name="translations" display-name="Translations" 

                    column-name="translation_id"      data-type="map"  
                    component-item-type="articleTranslation"  display-name="translations">
            </property>
        </table>
       
    </item-descriptor>
   
    <!--///////////////////////////////-->
    <!--     articleTranslation      -->
    <!--///////////////////////////////-->

    <item-descriptor name="articleTranslation" id-space-name="articleTranslation" 

         display-name="articleTranslation"  display-property="title">
       
        <table name="shop_article_xlate" type="primary" id-column-name="translation_id">
            <property name="title" column-name="title" data-type="string"  display-name="Title" />
            <property name="content" data-type="big string" column-name="long_description" 

                      display-name="content"/>
            <property name="location" data-type="string" column-name="article_location"

                     display-name="location" />
        </table>
       
    </item-descriptor>
</gsa-template>

==========================================================================

Advantages of this approach : 
  • Applications can switch between international and non-international modes without requiring any JSP  page changes. 
  • No database schema changes are required to add additional languages.

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.