Tuesday, 28 April 2015

Caching Store level or Site level widgets.

While creating cachespec.xml, decision about widgets can be either :
  1. Do not cache
  2. Cache separately
  3. Consume within parent servlet ( cache along with parent JSP )
Do not cache
Widgets like MiniShopCartWidget, InventoryWidget, DiscountWidget etc will not be cached. An entry in cachespec.xml with attributes do-not-cache and do-not-consume set to true will be sufficient for these widgets

    <cache-entry>
        <class>servlet</class>

        <name>/Widgets_701/xxx/InventoryAvailability.jsp</name>
        <name>/Widgets_701/Common/Discounts/Discounts.jsp</name>
        <name>/Widgets_701/xxx/Discounts.jsp</name>
        <property name="do-not-consume">true</property>
        <property name="do-not-cache">true</property>
            <cache-id>
            <component id="storeId" type="parameter">
                <required>false</required>
            </component>
        </cache-id>   
    </cache-entry>


Cache separately

Widgets like productRecommendations, CategoryRecommendations etc can be cached separately so that it can be reused across layout JSPs. An entry in cachespec.xml with attribute do-not-consume = true will be sufficient for these widgets 

    <cache-entry>
        <class>servlet</class>
        <name>/Widgets_701/xxxx/BreadcrumbTrail.jsp</name>
        <property name="save-attributes">false</property>
        <property name="ignore-get-post">true</property>
        <property name="consume-subfragments">true</property>
        <property name="do-not-consume">true</property> <!--  cache separately.. -->
        <property name="do-not-cache">false</property>

        <cache-id>
             <component id="DC_storeIdentifier" type="attribute">
                 <required>true</required>
            </component>

           ...
           ...
        </cache-id>
   </cache-entry>

  

Emarketing Widgets 

Emarketing spot widgets are handled separately. Since activities associated with eMarketing spots can be dynamic or static, the decision about whether to cache the widget or not should be done dynamically at run time based on the activities associated with widget. So general recommendation is to set do-not-cache = true and do-not-consume = true and let the marketing component decide whether to cache the widget or not and whether to cache separately or consume along with parent servlet.


Also you need to  include the following metaDataGenerator in cachespec.xml. This metaDataGenerator will make appropriate decision about caching and adding dependency Ids

<metadatagenerator>
     com.ibm.commerce.marketing.cache.EMarketingSpotMetaDataGenerator
</metadatagenerator>

Consuming widgets
Widgets like CatalogEntry widgets can be consumed within the parent layout JSP like CategoryDisplayPage or SubCategoryDisplay pages. 
When a widget is consumed by a parent JSP, no entries were made for the consumed JSP in cachespec.xml. But the dependency id's of the consumed JSP were added directly to the parent JSP cache entry in cachespec.xml. So assuming ProductPage imports PriceDisplay.jsp, price:<productId> dependency id was added to ProductPage. Whenever the price of a product changes, price:<productId> invalidation id will be published by server and hence ProdcutPage cache gets invalidated. 

When a Store is not using commerce composer tooling, IT developer will create the ProductPage and cachespec.xml. So IT developer will know that productPage consumes PriceDisplay widget and hence adds price dependency id's to the ProductPage cache entry.

But with commerce composer tool, business user has full control over layout widgets and can pick and choose the widgets which goes into layout. So during Code and UT phase, IT developer will have no idea about widgets present in a page and hence cannot add dependency Id's directly to the parent servlet.

For example, assume ProductPage contains price widget and hence developer adds price dependency id to ProductPage entry. Now for some reason, business user removes price widget from the ProductPageLayout widget and adds inventory widget in that slot. Now what happens when price changes ? The entire productPage gets invalidated, even though it doesn't display price. What happens when inventory changes? The page will still be served from cache and it doesn't display the updated inventory status. We cannot expect business user to take help of developers to modify the cachespec.xml as and when he changes the layout widgets.

The answer to this issue is <wcpgl:pageLayoutWidgetCache/> JSTL tag. When this tag gets executed, it adds the dependency id's to the parent servlet cache entry dynamically.

Follow these 3 high level steps to consume the widget with the parent servlet:
  1. Add <wcfpgl:pageLayoutWidgetCache/> to the main widget JSP.
  2. Make an entry in cachespec.xml for the widget JSP. Add all the cache-ids and dependency ids for the entry
  3. Set do-not-consume = true and do-not-cache = true.
Now if the widget is included in the layout by business user, then during runtime, the widget JSP gets executed. Since we have <wcpgl:pageLayoutWidgetCache/> tag inside the widget JSP, this tag gets executed. This tag reads the dependency ids from the cachespec.xml and sets it on the parent servlet. It also resets do-not-consume and do-not-cache values to false.

If the widget is not included in the layout by business user, then widget JSP doesn't get executed and parent page will not contain any additional dependency IDs.

So far so good. But what happens if someone wants to genuinely set do-not-consume and do-not-cache to true. How does someone tell that the values set in cachespec.xml shouldn't be modified by the included <wcfpgl:pageLayoutWidgetCache/> tag. This is where special dependency id ignoreDoNotConsume comes into picture. So if you want to let the pageLayoutWidgetCache tag to reset the do-not-consume and do-not-cache values and add dependency id's to the parent servlet, then add this dependency tag to the widget cache entry in cachespec.xml

<dependency-id>ignoreDoNotConsume</dependency-id>

 This special tag tells the pageLayoutWidgetCache handler to ignore the do-not-consume setting on cachespec.xml and reset it to false. If this dependency id is not present, then pageLayoutWidgetCache will not do any additional processing and lets the cache component of application server to handle it appropriately.

Use of pageLayoutCache JSTL tag and PageDesignMetaDataGenerator

Typically when servlet caching is enabled in WCS for Store JSPs, top level browsing pages like ProductDisplay, CategoryDisplay, HomePage are cached. These pages are cached on the input parameters to the view.

For example, ProductDisplay view will be cached on productId or partNumber, storeId, langId, catalogId. Once the page is cached, the next step is to identify the dependency Id's based on the content displayed by the view, so that the cache can be invalidated whenever the content of the page changes. 
So for example, if page displays both offerPrice and listPrice, then dependency Id's will be added for each price, so that even if one of the price changes entire cache gets invalidated.

Prior to Feature Pack 7, the ProductDisplay page was built by IT team and they typically updated the cachespec.xml to add dependency ids, cache attributes etc., based on page contents.

But starting with feature pack 7, business users have ability to use commerce composer to modify the page contents ( add / remove widgets ) as well as schedule the priority, start date / end date for the layouts. Which means, ProductDisplay page dependency Id's cannot be statically added to cachespec.xml during Code and UT phase. The dependency Id's needs to be added dynamically at runtime based on the layout attributes set by business users.

WebSphere Application Server cache module provides a way to programmatically decide whether to cache a page or not and also to add dependency Id's and set cache time out values at run time. This is achieved through MetaDataGenerator class and <metadatagenerator> tag in cachespec.xml.

WCS makes use of this feature to manage the cache properties dynamically at runtime.
 So a new class com.ibm.commerce.pagelayout.cache.PageDesignMetaDataGenerator is created and following tag is added to top level cache entries in cachespec.xml

<metadatagenerator>com.ibm.commerce.pagelayout.cache.PageDesignMetaDataGenerator</metadatagenerator>

PageDesignMetaDataGenerator gets executed for every request. This metaDataGenerator class will identify all the eMarketing Spots and activities responsible for displaying the layout. (Commerce composer uses Marketing runtime under the hood to associate layouts with pageObjects like product pages, category pages, static pages). Once the eMarketingSpots are identified, it checks if these eSpots are static or dynamic in nature. If the eSpots are static then metaDataGenerator decides to cache this page and sets the timeOut value based on the eSpot attributes. If the eSpot is dynamic then metaDataGenerator makes it non cacheable request.

Since business user have the ability to change the layout (add widget, remove widget, change widget properties ) using composer tool, the cache needs to be invalidated dynamically as and when business user changes the layout. 

Communication between composer tooling and layout cache happens through dependency id's and invalidation id's. Whenever business user modifies the layout, pageLayout:<layoutId> dependency id is published by the composer tool.
The cache entry will have pageLayout:<layoutId> as one of it's invalidation id. So when dependency Id pageLayout:<layoutId> is published, the cache gets invalidated.

But how will someone add pageLayout:<layoutId> as invalidation Id to the cache entry ? Obviously it cannot be added statically in cachespec.xml, since layoutId is determined at runtime.

One option is to make use of MetaDataGenerator to identify the layoutId. But as we know, MetaDataGenerator gets executed for every request ( even if request is eventually served from cache ). So identifying the layoutId by executing pageDesign handler in metaDataGenerator for every request is very expensive operation and defeats the whole purpose of caching.

This is where <wcpgl:pageLayoutCache> JSTL tag comes into picture. This tag needs to be included on every layout JSP page. This tag makes use of the pageDesign object available in JSP and retrieves the layoutId from pageDesign object. It adds pageLayout:<layoutId> as one of the dependency id to the cache entry. Since layout details already fetched in the JSP, the tag doesn't make any additional service call. Also when the request is served from cache, the JSP never gets executed and hence there is no overhead with this approach.

So in summary, whenever layout JSP ( the top level JSP, which has an entry in struts-config-ext.xml + makes a call to pageDesign object to retrieve the layout information ) is cached, the following additional steps should be done:

1) Add <metaDataGenerator> tag to the cache entry in cachespec.xml - This tag decides whether to cache the request or not. If cached, adds timeOut value and emsName:<name> as one of the dependency id. This helps to invalidate the cache whenever layout association changes.
2) Add <wcfpgl:pageLayoutCache> into JSP - This tag adds pageLayout:<layoutId> as one of the dependency id, so that when layout design changes, the cache gets invalidated.


Wednesday, 22 April 2015

Invoking REST Services ( PUT / POST / DELETE ) from Store JSP

In this section we will look at how to invoke REST Services ( POST / PUT / Delete ) from Store JSPs using WCS framework.

Let's assume that you want to update quantity of an existing orderItem using REST Service. Looking at the swagger documentation we see that the REST URL to update an orderItem is:


https://localhost/wcs/resources/store/10001/cart/@self/update_order_item
HTTP Method is: PUT


Now to test this REST URL, you can use any REST Client (like HTTP Requester for FF or POSTMAN for chrome) and do a PUT with the JSON body set to something like below:


{

"orderId" : "123",
"orderItem" : [
      {
            "quantity" : "4", // New quantity
            "orderItemId" : "1212",
      }
   ]
}

 
Lets see how you can invoke this REST Service from JSP. Say, you have a shopping cart page where customers are allowed to modify the quantity and hit a submit button to save it. So how do you invoke a REST action and POST / PUT a JSON body as part of form submission ? Who converts the name value pair to JSON input required for the REST call ?

WCS framework provides you an Action class (RESTAction) to achieve this in a much simpler way. Its as simple as invoking a controller command from a view. Following list gives you step by step instructions to do the same:

  1. Create a mapping in rest-template-config.xml in Stores.war/web-inf/config/com.ibm.commerce.order-fep/rest-template-config.xml OR in your own XML file inside Stores.war/web-inf/config/<yourDir>. Make sure your XML file name ends with rest-template-config.xml 
rest-template-config.xml Mapping

  • Resource path ${serverHost}/wcs/resources/store/${storeId}/cart is mapped to name "orderlist".
  • HTTP PUT Method for path @self/update_order_item is mapped to name updateOrderItem
  • Input name value parameters are mapped to JSON structure inside <template> tag.
Update struts-config-order-rest-services.xml (or your custom struts file) to create a mapping URL as shown below:

Struts mapping entry.
  • parameter value refers to resourcePathName.methodName defined in rest-template-config.xml.
  • type is set to AjaxRESTAction. If you like to invoke the REST Service in Web1.0 style without using Ajax, then change type to RESTAction
With the above setup done, you can now submit the HTML Form to "AjaxRESTOrderItemUpdate" and pass name value parameter.

So the final URL to which HTML form will be posted looks something like:
https://localhost/webapp/wcs/stores/servlet/AjaxRESTOrderItemUpdate?orderId=123&orderItemId=123&quantity=4

This request is handled by the Store Struts servlet which invokes AjaxRESTAction class. This action class builds the complete REST URL and the JSON body using the mappings present in rest-template-config.xml and invokes that URL either remotely (using java.net.URL object) or locally (using RequestDispatcher).

You can follow similar pattern to make POST and DELETE calls from Store JSP