Showing posts with label ATG. Show all posts
Showing posts with label ATG. Show all posts

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.

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

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

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.

Wednesday, 27 July 2016

Behind the Scenes | ATG Droplet [Servlet Bean] Internal Processing

Today found some interested information about ATG droplet processing.


When a JSP executes a servlet bean, the dsp:droplet cycles through its code internally several times in order to arrange the servlet bean code in a manner that is cohesive with the expectations of open parameters.

For example below code.
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> 
  <dsp:param name="array" bean="/samples/Employees"/>
     <dsp:oparam name="output">
        <dsp:valueof param="element.name"/>
     </dsp:oparam>
 </dsp:droplet>

Processing Steps :

1. The dsp:droplet tag is called.

2. dsp:droplet allows its body to be executed once. During that execution, the nested input parameter tags (in this case, just array) pass their information back to dsp:droplet, which uses it to construct a table of input parameter names (array) and values. The open parameter tags are ignored during this phase.

3. dsp:droplet finds the servlet bean referenced by the dsp:droplet “name=" property (forEach) and calls the servlet bean’s service() method with the input parameter values collected during step 2.

4. As the servlet bean executes, it halts calls to setParameter and serviceParameter, and instead records them as a list of DropletActions. These methods are organized in a manner that is readable by the open parameters that process them and are made available to open parameters for execution.

5. The dsp:droplet parses through each setParameter and serviceParameter method in DropletActions.
  • setParameter directs the dsp:droplet to set the specified request parameter to the recorded name (ouput) and value (element).
  • serviceParameter instructs the dsp:droplet to allow its body to be executed. This causes the related open parameter to run.
6. After the dsp:droplet finishes the DropletActions list, servlet bean execution ends.