Friday, April 30, 2010

RESTful page caching using request parametric signature.

This article discusses how a web application could avoid regenerating a dynamic web page in the situation where a page is requested more than once or by more than one user.

I implemented this strategy in 2004 while employed with a semiconductor company in Massachusetts - i.e., it's tried and tested (using plain SQL rather than JPA and JSP rather than GWT).


The problem
There are two issues causing web page regeneration.
  1. A user needing to go back to previous pages for some information and then go forward again.
  2. Two or more independent users requesting for the same dynamically generated web page.

These issues are especially aggravating for pages that take a long time to generate.
e.g., a car-hotel-airline booking site scanning its repositories for the best deal, or an enterprise report that is generated by heavy mathematical calculations extracted from a complex combination of data sources.

There are three strategies that can help to solve this issue.
  1. Cache the web page.

  2. Cache the data extract used for generating the web page.
    A web page may need to include attributes individualised for each user and request even though the same information is being displayed. For example, the respective user name and attributes.

    This strategy is worthwhile when the complexity of extracting the data for constructing the web page is very resource consuming and that there are too many individualised user attributes on a page.


  3. Cache the components of the web page and regenerate the web page from those components.
    This is a compromise between the first two strategies. All components of the page are generated and cached except for the individualised page attributes.

Identifying web requests
In any of these solutions, there is one common and significant question to be answered first - how to identify the similarity of web requests. The simple answer is, of course, from the request parameters.

However, the answer to the question is not that simple. There are further three issues to consider before using the request parameters as page identity.
  1. Presence of parameters that do not contribute to the identity of a web page. Such additional parameters would make some web requests seem different when they are actually requesting for the same information.

  2. A cached page would become stale, when fresh data is available affecting the information being requested. Therefore, two requests with the same set of parameters may not be asking for the same web page after the the data repository has been updated.

  3. The identity of a web page is due to a large set of parameters. This is especially true for mathematically generated reports.
The solution is using a parametric signature to identify the web page.


Page signature from request LCD
First, the application architect has to design the LCD (lowest common denominators) of the request parameters of a web page. The architect should design web pages to be identifiable by the least number of request parameters.

Second, every http request to the web application must first go through a signature generator. The signature is the compressed value of LCD parameters. The signature is compared against a cache signature table in a database. If the signature is non-existent, it is stored into the table and a fresh web page is generated from the request parameters. If the signature exists, the cached page is served.

What if two or more requests arrive at the same time requesting for the same uncached page? There would be a race to create the signature record in the table. Therefore, a unique indexed-key has to be defined for the signature field.

The cache signature table comprises the columns (using JPA/JDO pseudocode):
@entity class CacheParamSig{
  @id String signature;
  String reservation;
  Datetime requestDatetime;
}

The algorithm
Let's say that a request is received. It is run through the parametric signature generator:
String paramSig = ParamSigGenerator.generate(request);

Each request races against any other to grab the CacheParamSig record and update it with its reservation semaphore:
String reservation = servername + session.getId();
The datetime of that reservation:
Datetime reservationTime = new Datetime();

First, determine if the page is cached, stale or non-existent:
if (cacheParamSig exists){

  // is another request generating the page?
  if (cacheParamSig.reservation != null){
    return PageIsGenerating;
  }

  else { // page is already generated and cached
    TimeDeterminant =
      Use request parameters to determine what database tables will be used
        to generate the page;

    // Get the latest upload time of those tables
    Datetime latestData = getLatest(TimeDeterminant);

    // is cached page stale?
    if (latestData > cacheParamSig.requestDatetime)
      grab the CacheParamSig record to generate a fresh page;

    else
      deliver the cached page to the browser;
  }
}


AJAX polling required
The browser page sending the request should be an AJAX polling loop, which should be easily implemented using GWT RPC. On receiving PageIsGenerating, the requesting page should wait and then attempt to send the request again.

If the page is stale or non-existent, the request proceeds to grab the CacheParamSig record.


Grabbing the reservation
Now, the request starts to race to grab and place a reservation on the CacheParamSig record:
transaction {
  CacheParamSig cacheParamSig =
    get CacheParamSig record using id paramSig;
  if (cacheParamSig exists){
    cacheParamSig.reservation = reservation;
    cacheParamSig.requestDatetime =  reservationTime;
    update cacheParamSig record;
  }
  else {
    cacheParamSig = new CacheParamSig();
    cacheParamSig.signature = paramSig;
    cacheParamSig.reservation = reservation;
    cacheParamSig.requestDatetime =  reservationTime;
    insert cacheParamSig record;
  }
}
at end of transaction {
  if (if transaction failed because another transaction on the record is running){
    rollback;
    return PageIsGenerating;
  }
}

In JPA-pseudocode,
the grabbing reservation race:
try
{
  transaction.begin();
  CacheParamSig cacheParamSig =
    (CacheParamSig)entityManager.find("CacheParamSig", paramSig);

  // if another request is already generating the page
  if (cacheParamSig.reservation != null){
    transaction.rollback();
    entityManager.close();
    return pageIsGenerating();
  }

  TimeDeterminant determinant = TimeDeterminant.get(request);
  Datetime latest = TimeDeterminant.latest(determinant);

  // if cached page is not stale
  if (latest < cacheParamSig.requestDatetime){
    transaction.rollback();
    entityManager.close();

    // paramSig value points to the cache where the page is stored
    return generatedPage(paramSig);
  }

  // otherwise, reserve the record to generate the page
  cacheParamSig.reservation = reservation;
  cacheParamSig.requestDatetime =  reservationTime;
  entityManager.merge(cacheParamSig);
  transaction.commit();
}

// page does not exist
// create and reserve the record to generate the page
catch (EntityNotFoundException e){
  cacheParamSig = new CacheParamSig();
  cacheParamSig.signature = paramSig;
  cacheParamSig.reservation = reservation;
  cacheParamSig.requestDatetime =  reservationTime;
  entityManager.persist(cacheParamSig);
  transaction.commit();
}

finally
{
  // if another transaction is already generating the page
  if ( transaction.isActive())
  {
    transaction.rollback();
    entityManager.close();
    return pageIsGenerating();
  }
}

// the page is cached into the folder named by the String value of paramCacheSig
generatePageIntoCache(request, paramCacheSig);


After generating the page,
  • end the reservation to tell others possibly waiting for the page, that the page generation has completed,
  • and send the page to the requesting page.
try
{
  transaction.begin();

  // tell others possibly waiting that the page generation has completed
  cacheParamSig.reservation = null;
  transaction.commit();
}
finally
{
  // this is not possible, otherwise something's wrong
  // but catch it anyway just in case

  if ( transaction.isActive())
  {
    transaction.rollback();
    entityManager.close();
    return BigTimeError;
  }

  return generatedPage(paramSig);
}


Further minutiae
This article presents a skeletal. There are minutiae that needs to be taken care of.
  1. Need to treat the case where cached page was not stale, but just at the end of transaction, another request came in and found it stale and reserved the ParamCacheSig record to regenerate the page and is in the process of overwriting the cache while you are attempting to send the cache to the requesting page.
  2. If pageIsGenerating, The GWT RPC response should notify the requesting page each time when the current reservation was started.
  3. A job needs to be scheduled to delete stale ParamCacheSig records, to prevent the table from growing. Depending on the distribution of page possibilities, the schedule could be hourly, daily, weekly, etc.
  4. The skeletals above illustrate only the strategy of caching whole pages. Pages need not be HTML. They could be GIF, XLS, CSV, text or a combination various output formats.
Normally, there are two modes of page generating - scheduled reports and ad-hoc pages. When I implemented this strategy, I merged both modes. I created a ScheduledReports table, where each record contains a scheduled report name and its request parameters  and a JSP interface to that table for users to schedule generation of their pages.

I wrote another JSP to read this table and then to perform a http request to generate the respective page. A job scheduler was used to invoke that JSP.


Page sequence tracker
Another interesting feature I had was a page sequence tracker. In statistical analysis, frequently, the output of a page is fed as input into another page. At the start of analysis, the user would check the box for Start page sequence tracker. In statistical analysis, an analyst would try all sorts of sequences of analysis until he/she hits the jackpot of the perfect sequence. Once the jackpot is hit, the analyst would want to turn those adhoc page requests into a scheduled report. The page sequence tracker merely writes the sequence of parameters into the ScheduledReports table under a sequence group, where the reports are generated sequentially at the scheduled time.

With adhoc and scheduled pages generation performed under the same mechanism, I was even able to analyse the most frequently used parameters and turned them into scheduled reports without users knowing - and occasionally, users were congratulating me on how unbelievably quick the response to some of their more complex statistical queries had become. The pages were generated one hour before they normally sat down to start their analysis after their morning cup of coffee.

Thursday, April 29, 2010

Patch-extending an insider-traded UIBinder widget

This a continuation of the rant on insider-traded UIBinder widgets.

In this example, we wish to extend the behaviour of
com.google.gwt.user.client.ui.MenuBar. UIMenuBar is to be a patch-extension of MenuBar by retaining the package namespace com.google.gwt.user.client.ui.

MenuBar widget has an addSeparator() method. However, the addSeparator() can be used only in Java code but not in UIBinder becausee the insider-traders of UIBinder somehow left that out. Therefore, UIBinder does not have the feature to add a separator between MenuItems.

This tutorial illustrates extending MenuBar so that a separator could be added as a MenuItem of a MenuBar when using UIBinder.

package com.google.gwt.user.client.ui;

public class UIMenuBar
    extends MenuBar
{

    public UIMenuBar() {}

    public UIMenuBar(boolean vertical) {
        super(vertical);
    }

    public UIMenuBar(Resources resources) {
        super(resources);
    }

    public UIMenuBar(boolean vertical, Resources resources) {
        super(vertical, resources);
    }

    @Override
    public MenuItem addItem(MenuItem item) {
        if (item.getText().equals("!")){
            super.addSeparator();
            return null;
        }
           
        return super.addItem(item);
    }
}

Notice the package declaration of UIMenuBar masquerades as the same package as MenuBar's. This is because just as GWT compiler recognises UIMenuBar as MenuBar (due to inheritance), the GWT compiler would attempt to look for the MenuItem class within the same package.

Therefore, if we had extended UIMenuBar as
org.synthful.holymoly.UIMenuBar, the GWT compiler, due to insider-trading, would attempt to look for MenuItems class under the namespace org.synthful.holymoly rather than com.google.gwt.user.client.ui. Then we would have to extend the whole repertoire of classes associated with MenuBar, just to package them as org.synthful.holymoly. But, the shortcoming is only with MenuBar while its associated classes and widgets are rather well behaved, already UIBinder-friendly classes with which we don't wish to mess around.

Note the overridden addItem method. A separator is added instead, if the text of the MenuItem is "!".

Now you can use UIBinder to insert separators into your MenuBar:
<!DOCTYPE ui:UiBinder SYSTEM
  "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder
  xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:gz="urn:import:org.synthful.gwt.widgets.client.ui"
  xmlns:g="urn:import:com.google.gwt.user.client.ui">
  <ui:with field='mainMenu'
  type='holymoly.main.client.MainMenuBar'/>
  <g:UIMenuBar ui:field="menuBar">
   <g:MenuItem ui:field="edit">Edit
     <g:MenuBar ui:field="editMenu" vertical="true">
       <g:MenuItem ui:field="editFile"
         command="{mainMenu.getEditProfileCmd}">Edit File</g:MenuItem>
     </g:MenuBar>
   </g:MenuItem>
   <g:MenuItem>!</g:MenuItem>
   <g:MenuItem ui:field="exit"
     command="{mainMenu.getExitCmd}">Exit</g:MenuItem>
  </g:UIMenuBar>
</ui:UiBinder>

The listing for UIMenuBar is at http://code.google.com/p/synthfuljava/source/browse/#svn/trunk/gwt/widgets/com/google/gwt/user/client/ui.

Extending SmartGWT widgets to be UIBinder-compliant

SmartGWT canvas widgets can seldom share space with GWT widgets. The SmartGWT canvas would push itself in-front to obstruct the visibility of GWT widgets. To share a space between the two sets of widgets, the page design needs to carefully avoid having GWT widgets intersecting into SmartGWT canvas, vice versa. Especially that any part of a GWT sub-menu or pop-up that pops into SmartGWT canvas space would not be visible.

Having understood this constraint, this article describes placing extension wrappers around SmartGWT canvas widgets to allow them to be used as UIBinder widgets. Also note that certain newer SmartGWT widgets do not work well with UIBinder.

As described in a previous article, any GWT to be used as UIBinder widgets should
be
  • an extension class of com.google.gwt.user.client.ui.Widget and
  • implement com.google.gwt.user.client.ui.HasWidgets.

The first SmartGWT widget to extend is, of course, com.smartgwt.client.widgets.Canvas, because all SmartGWT has to be placed onto a Canvas.

import org.synthful.smartgwt.client.HasWidgetsUtil;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.widgets.Canvas;

public class UICanvas
    extends Canvas
    implements HasWidgets
{
    @Override
    public void add(Widget w) {
        super.addChild(w);
    }

    @Override
    public Iterator<Widget> iterator() {
        return HasWidgetsUtil.iterator(this);
    }

    @Override
    public boolean remove(Widget w) {
        return HasWidgetsUtil.remove(this, w);
    }

}
Notice the HasWidgetsUtil import highlighted in crimson. HasWidgetsUtil contains common static methods which could be used in most wrappers.

For a Canvas object that does not inherit from com.google.gwt.user.client.ui.Widget, we need to wrap it with a class that extends Widget and expose all the methods of that Canvas object. For example, while com.smartgwt.client.widgets.tab.TabSet inherits Widget, com.smartgwt.client.widgets.tab.Tab does not.

Wrapping Tabset is as simple as wrapping Canvas:

public class UITabset
    extends TabSet
    implements HasWidgets
{   
    @Override
    public void add(Widget w){
        if (w instanceof UITab)
            super.addTab(((UITab)w).tab);
        else
            super.addChild(w);
    }
   
    @Override
    public Iterator<Widget> iterator(){
        return HasWidgetsUtil.iterator(this);
    }

    @Override
    public boolean remove(Widget w){
        return HasWidgetsUtil.remove(this, w);
    }
}
However, to be able to use UIBinder to assign a Tab as a child of a Tabset, we need to wrap the Tab class this way:

package holymoly.smartgwt.ui

import java.util.ArrayList;
import java.util.Iterator;
import org.synthful.smartgwt.client.HasWidgetsUtil;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.tab.Tab;

public class UITab
    extends Widget
    implements HasWidgets
{
    public UITab(){
        this.tab = new Tab();
    }
       
    public void add(Canvas w){
        if (this.canvas==null){
                this.canvas = (Canvas)w;
                tab.setPane(this.canvas);
            }
    }
   
    @Override
    public void add(Widget w){
        try{
            this.add((Canvas)w);
        }
        catch(Exception e){}
    }
   
    @Override
    public void clear()    {
    }
   
    @Override
    public Iterator iterator()    {
        ArrayList<Widget> wx = new ArrayList();
        wx.add(this.canvas);
        return wx.iterator();
    }

    @Override
    public boolean remove(Widget w){
        return HasWidgetsUtil.remove(this, w);
    }

    public void setCloseable(boolean closeable){
        this.tab.setCanClose(closeable);
    }
   
    public void setCloseIcon(String closeIcon){
        this.tab.setCloseIcon(closeIcon);
    }
   
    public void setCloseIconSize(String closeIconSize){
        try{
            int z = Integer.parseInt(closeIconSize);
            this.tab.setCloseIconSize(z);
        }
        catch (Exception e){}
    }
   
    public void setDisabled(boolean disabled){
        this.tab.setDisabled(disabled);
    }
   
    public void setTitle(String title){
        this.tab.setTitle(title);
    }
   
    public void setWidth(String width){
        try{
            int z = Integer.parseInt(width);
            this.tab.setWidth(z);
        }
        catch (Exception e){}
    }
   
   
    final protected Tab tab;
    private Canvas canvas;
}

Now we could comfortably use these SmartGWT widgets with UIBinder:
<ui:uibinder
  xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:z="urn:import:holymoly.smartgwt.ui"
  xmlns:g="urn:import:com.google.gwt.user.client.ui">
  <z:UICanvas ui:field="canvas">
   <z:UITabset ui:field="tabset">
    <z:UITab ui:field="tab1"> closeable="true"
     closeIcon="{someResource.someicon}"
     <g:Label ui:field="tab1content">
      Hello Galaticals!
     </g:Label>
    </z:UITab>
   </z:UITabset>
 </z:UICanvas>
</ui:uibinder>

More examples are found at
http://code.google.com/p/synthfuljava/source/browse/#svn/trunk/gwt/smartgwt.

Wednesday, April 28, 2010

Extending widgets for use with UIBinder

UIBinder, as released with GWT 2, is convenient because it allows us to specify our UI structure through XML.

However, the maturity of UIBinder is left in question because many useful GWT widgets are quite unusable in UIBinder, placing in question the deployment capability of UIBinder in any practical project. Not that they are unusable, but UIBinder allows them to function only as child widgets, and hence, incapable of having children within the UIBinder framework.

This article tells you how to write wrappers around GWT widgets to make them available for use as parent widgets with UIBinder. In fact, you can even place such wrappers around SmargGWT widgets.

Rule: Must implement HasWidgets Interface.
Any GWT widget to be used with UIBinder must implement the interface
com.google.gwt.user.client.ui.HasWidgets.

HasWidgets interface requires your wrapper to make the following methods available.

void add(Widget w);
void clear();
Iterator<Widget> iterator();
boolean remove(Widget w);

This is especially true with widgets that inherit the restriction of the SimplePanel abstract widget. SimplePanel widgets can only have one child and a child is acquired through the setWidget method, without implementing the HasWidgets interface.

Exception to Rule: Otherwise, patch-extend an insider-traded widget.
There are existent UIBinder widgets which do not implement HasWidgets. This is due to GWT insider-trading with some of the repertoire of UIBinder widgets - the GWT compiler recognises the type of those widgets and has insider-coded their UIBinder child and attribute specification and constraints. The only way to overcome the unfair advantage these widgets have is by patch-extending them. We fool the GWT compiler into believing it is compiling one of its own insider-traded widgets by extending such widgets.

You might be asking, if a widget is already capable as a parent widget with UIBinder, what is the point of wrapping them? The reason is to expand the repertoire of widgets allowed to be its children. Since the GWT compiler has already insider-coded what such widgets are allowed to have as children.

What I mean by patch-extending is, placing the extension class within the same package namespace - by stealing into the namespace com.google.gwt.user.client.ui. Primarily, due to needing access to default and protected objects of the original widget.

And, what I mean by insider-trading of UIBinder widgets is the existence of parsers specialised for a bunch of widgets, where we (end-user-programmers) have scantly any way of inserting our own parser:
http://code.google.com/p/google-web-toolkit/source/browse/#svn/trunk/user/src/com/google/gwt/uibinder/elementparsers. Unless we built our own gwt-user.jar.

Wednesday, April 14, 2010

DialogPanel - DialogBox Enhancement Revisit

In http://h2g2java.blessedgeek.com/2009/07/gwt-useable-closeable-scrollable.html, I had presented a skeletal to a DialogBox with a close button on its caption bar. That article explains the reason for such an effort is because the GWT architects have gone through extraordinary lengths to ensure we cannot extend the caption bar to modify its behaviour.

However the alignment of elements in the caption bar of that skeletal is simply skeletal, without CSS to align them. Therefore, I wrote an improvement that includes alignment CSS, but it is still skeletal because the background of the caption bar is transparent and that leaves it to you to filling in the background.

This improvement class DialogPanel, besides having a more presentable caption bar,
  1. uses UIBinder to define the caption bar,
  2. extends DecoratedPanel rather than extending or patching DialogBox,
  3. no longer has a ScrollPanel, because scroll feature is best left to the end-user-programmer by setWidget method.
The event handling scheme is exactly the same as in http://h2g2java.blessedgeek.com/2009/07/gwt-useable-closeable-scrollable.html, so you should refer to that article on the handling of events for DialogPanel.

The source code is found in google code repository at
  1. http://code.google.com/p/synthfuljava/source/browse/trunk/gwt/widgets/org/synthful/gwt/widgets/client/ui/DialogPanel.java and
  2. http://code.google.com/p/synthfuljava/source/browse/trunk/gwt/widgets/org/synthful/gwt/widgets/client/ui/DialogPanel-Caption.ui.xml.

My next plan is to make the DialogPanel resize-able by mouse. So, keep a lookout for its enhancement.