Size of collection in a JSP/DSP page

leaf pile on Flickr

(Photo: leaf pile by oeimah)

Sometimes in a JSP/DSP page you will want to get the size of a collection and unless you are within a Range, ForEach or similar droplet you won’t have access to this value.

Struts has a nice solution using the bean:size tag.  JSTL 1.1 has a nice solution using the fn:length function.

Here is an example of how to use Struts, DSPEL and JSTL to get the size of a collection.

  <dspel:getvalueof param="book.pages" var="pages"
                    vartype="java.util.Collection"/>
  <bean:size id="numPages" name="pages"/>
  Number of Pages: <c:out value="${numPages}"/>
  Number of Pages: <dspel:valueof value="${numPages}"/>

Here is an example of how to use JSTL 1.1 and DSPEL to get the size of a collection.

 

  <dspel:getvalueof param="book.pages" var="pages"
                    vartype="java.util.Collection"/>
  Number of Pages: <c:out value="${fn:length(pages)}"/>

Accessing RepositoryItems with JSTL

Often we are accessing repository items in a JSP page like this.

  <dspel:droplet name="RQLQueryForEach" var="query">
    <dspel:param name="repository" bean="/betweengo/repository/Repository"/>
    <dspel:param name="itemDescriptor" value="Account"/>
    <dspel:param name="queryRQL" value="ALL"/>
    <dspel:setvalue param="account" paramvalue="element"/>
    <dspel:oparam name="output">
      <dspel:valueof param="account.name"/><br/>
    </dspel:oparam>
  </dspel:droplet>

With JSTL we could try to display the name like this.

      <c:out value="${query.account.name}"/>

If you are using JSP 2.0 you can display it even more simply.

      ${query.account.name}

However since account is a RepositoryItem object and there is no get method for the name property (i.e. it’s not a JavaBean object) the above will fail and produce an exception.

To get around this you can extend ATG’s RQLQueryForEach class by overriding the protected setElementParameter. In addition to setting the element parameter in the request you can set a new parameter which we will call “item”. This item is of class atg.beans.DynamicBeanMap and wraps a RepositoryItem.

public class RQLQueryForEachEL extends RQLQueryForEach {
  protected void setElementParameter(DynamoHttpServletRequest pRequest,
                                     String pElementName, Object pValue) {
    super.setElementParameter(pRequest, pElementName, pValue);
    DynamicBeanMap itemBean = new DynamicBeanMap(pValue, true);
    pRequest.setParameter("item", itemBean);
  }
}

Now we can access the repository item with JSTL like this.

      ${query.item.name}

Comparison of DSP and DSPEL

ATG has two JSP tag libraries, DSP and DSPEL. Both have similar syntax but DSPEL allows you to use JSTL.

Here’s a simple comparison of how you would use DSP versue how you would use DSPEL to do a simple RQL query.

DSPEL

<dspel:droplet name="/atg/dynamo/droplet/RQLQueryForEach">
  <dspel:param name="repository" bean="/betweengo/Repository"/>
  <dspel:param name="itemDescriptor" value="Merchant"/>
  <dspel:param name="queryRQL"
    value="name EQUALS \"${requestScope['nm']}\""/>
  <dspel:oparam name="empty">
    No merchant with the name "<c:out value="${requestScope['nm']}"/>".
  </dspel:oparam>
  <dspel:oparam name="output">
    Name: <dspel:valueof param="element.name"/>
  </dspel:oparam>
</dspel:droplet>

DSP

<% String query = "name EQUALS \"" + request.getAttribute("nm") + "\"";%>

<dsp:droplet name="/atg/dynamo/droplet/RQLQueryForEach">
  <dsp:param name="repository" bean="/betweengo/Repository"/>
  <dsp:param name="itemDescriptor" value="Merchant"/>
  <dsp:param name="queryRQL" value="<%= query %>"/>
  <dsp:oparam name="empty">
    No merchant with the name "<%= request.getAttribute("nm") %>".
  </dsp:oparam>
  <dsp:oparam name="output">
    Name: <dsp:valueof param="element.name"/>
  </dsp:oparam>
</dsp:droplet>

ATG Consulting Interview

Today I had the most detailed but at same time most interesting ATG consulting interview yet. Here are the questions I was asked with answers when appropriate in italics.

  1. Which versions of ATG have you worked with?
  2. What parts of ATG’s stack have you worked with?
  3. How do you compare ATG with Ruby on Rails?
  4. What is Nucleus?
  5. What is the ATG Repository?
  6. When creating form handlers typically what ATG base class do you extend? GenericFormHandler.java
  7. When creating droplets what ATG base class do you extend? DynamoServlet.java
  8. What form handlers and methods do you use during checkout? ShoppingCartFormHandler, numerous handlers like handleMoveToConfirm, etc.
  9. What does a user typically see during checkout? Add to shopping cart, login, billing and shipping address, payment, confirm, confirmation, email confirmation, shipped email.
  10. How do you compare strings in Java? If String a = “hello” and String b= “hello” what is a == b? True, a and b both reference the same constant string.

In another interview I was asked these questions.

  1. What is HTTP? How does it work?
  2. If HTTP is stateless then how does a web application maintain state.

Slot not global warnings in ATG logs

In the ATG logs you may see warnings about a slot being session scoped not global.

[STDOUT] Invalid attempt to resolve component /atg/registry/Slots/ActivationFlowSlot in scope global. It is defined in scope session 

Most of the time you can safely ignore these warnings because these warnings are most likely coming from the ACC.

When browsing slots using the ACC the ACC tries to look up the slot as a global component and complains it’s a session scoped component.  In other words it’s an ACC bug that will probably never be fixed since ATG is moving away from the ACC.

Shop.com Product Display Integration

Last year for Casual Male I did the Shop.com Product Display Integration (PDI) which allows Casual Male to sell its products on Shop.com.

The integration involved three steps.

  1. Access all the products organized by category using ATG’s GSA Repository.
  2. Export the products into an XML file according to the PDI DTD using the XStream library.
  3. FTP upload to Shop.com’s servers using Apache Commons Net library.

Persisting Orders

When troubleshooting orders it is really helpful to be able to view the order in the ACC.

By default orders are saved because persistOrders property of the /atg/commerce/ShoppingCart component is set to true. If you don’t see incomplete orders being saved then this means this property is not set to true.

More information on Troubleshooting Order Problems can be found in the ATG Commerce Programming Guide.

DistributorSender startup warning

When starting ATG instances with the DCS module I will often see this warning.

DistributorSender No remote servers configured

This is because in the product catalog two item descriptors, media-internal-binary and media-internal-text, are configured to use the Dynamo content distributor system if it is available. This is documented in the Using Content Distribution with a SQL Content Repository section of the ATG Dynamo Programming Guide.

Since out of the box the Dynamo content distributor system is not configured we see the above warning. To turn it off simply set loggingWarning=false for the /atg/commerce/catalog/ContentDistributorPool component.

ClientLockManager useLockServer property is false warning

In many ATG instances I will see this warning upon startup.

/atg/dynamo/service/ClientLockManager The useLockServer property of: /atg/dynamo/service/ClientLockManager is false and so global locking is disabled

As explained in the Enable the Repository Cache Lock Managers section of the Installation and Configuration Guide for DAS the ClientLockManager must be enabled if you are using locked mode caching anywhere in your SQL repository, which is typically the case in production when you are running more than one ATG server. The above warning is to inform the ATG administrator that currently the ClientLockManager is not enabled.

To enable it follow the instructions in the Configuring Lock Managers subsection of the Locked Caching section of the ATG Repository Guide.

On a single ATG instance environment you might still choose to setup the ClientLockManager and ServerLockManager just to get rid of this warning. To do this do the following.

  1. Enable ClientLockManager by creating localconfig/atg/dynamo/service/ClientLockManager.properties.lockServerAddress=localhost
    lockServerPort=9010
    useLockServer=true
  2. Start ServerLockManager by adding it to localconfig/atg/dynamo/service/Initial.properties.initialServices+=ServerLockManager

After doing this though you’ll see a warning like this.

/atg/dynamo/service/ServerLockManager No backup server is configured for Lock ServerCRAPBOOK-PRO/localhost:9010 It becomes Primary Server

So maybe in the end it’s better just to ignore the ClientLockManager warning, maybe even disable it. 🙂

Debugging a Category’s Bad Child Products

Today I was using the CategoryLookup droplet to find a category’s child products. However when I accessed the child products I would get this JDBC error.

java.lang.NullPointerException
at java.lang.String.(String.java:166)
at oracle.sql.CharacterSet.AL32UTF8ToString(CharacterSet.java:1517)

I realized my product data was corrupted. However this category had over 70 child products. The stack trace wasn’t telling me which one was corrupt and going through each child product to find out which one was corrupt was too painful.

I first queried the database to find all the child product ID’s.

SQL> select child_prd_id from dcs_cat_chldprd where category_id='cat101' order by sequence_num;

I then created a simple JHTML page which would query each child product and output which ones were corrupted.

<java>
final String [] product_ids = { "prod101", "prod102", "prod103" };

for (int ii = 0; ii < product_ids.length; ii++) {
out.print(ii + ". [" + product_ids[ii] + "] ");
try {
</java>
<droplet name="/atg/commerce/catalog/ProductLookup">
<param name="id" value="`product_ids[ii]`">
<oparam name="output">
<valueof param="element.displayName"/><br/>
</oparam>
</droplet>
<java>
} catch (RuntimeException exc) {
out.println(exc + "<br>");
}
}
</java>

Once I knew which child products were bad I removed their mappings to the category in dcs_cat_chldprd.

SQL> delete from dcs_cat_childprd where child_prd_id = 'prod102' and category_id='cat101';

Then I updated the sequence numbers so that they are all consecutive by moving the ones at the end to fill the holes created by the previous deletes.

SQL> update dcs_cat_childprd set sequence_num = 1 where child_prd_id = 'prod103' and category_id='cat101';