Saturday, December 6, 2008

GXT Tree and Loading ... Alternative Design (Validated)

In my previous blog I had proposed a design which I could use to dynamically load the tree based on several different modeled objects. I was successful in implementing this. One of the tricks was to ensure the stores were loaded prior to building the tree, and to do this, I wrote a handler which would load each of the stores and listen for the completion of the loads and then build the tree from the model structures.

YSlow and Cache-Headers

If you run YSLow on one of your web applications, you can obtain some really useful information about improving the performance of your application. One common performance improvement is to set expiration headers on static resources (such as CSS, JS and image files). Setting this header, will help preserve the file in the browser cache, thus preventing reload of the image on subsequent page refreshes. Web servers like Apache make this easy (with a configuration file change). However, if you are using Tomcat both as a Web server and application container, the solution here is relative easy, but requires some coding. To facilitate this in Tomcat, you need to create a ServletFilter to add the Cache-Control header to the response, and then register your filtered types in your web.xml. A blog on this was posted by Byron Tymvios on jGuru here.

Tuesday, December 2, 2008

GXT Tree and Loading ... Alternative Design

This is a followup blog to my previous blog entry on GXT Tree and Loading from Restful URLs. While this approach worked, I had concerns over the number of AJAX requests as the tree is fully "expanded". As well, I was considering another possible issue:
  • The BaseModel objects representing the Country, Album objects are used elsewhere in the client (not just within my tree).
In this sense, the tree model is really a "view" into the real data. Having the tree be dynamically loaded prevents me from really being able to easily sync up changes made to the real data used elsewhere. I had already recognized that I likely needed to provide a singleton store pattern for obtaining countries and albums. The only problem was I was using this for lists and selection drop-downs which are modeled as ListStores (which co-incidentally are incompatible with TreeStores). However the fact that I have data being manipulated in two places just seems rather dangerous to me. For this reason, I think the process I intend to use here is still to use a DataProxy for the TreeStore, but instead have it's load method pull values from the corresponding singleton CountryStore and AlbumStore, and then provide some form of a Binder such that I can bind my TreeStore to the CountryStore (thus when a Country is created, deleted or modified, the TreeStore will be notified of the change in the CountryStore and take appropriate action). This also means I might be able to use a lighter-weight "representation" of the Country in the Tree (for example, just the "name" (for display) and "id" (as the foreign key to the CountryStore). In this case, my tree would really boil down to a BaseTreeModel object with three attributes ("name", "id", "type"). I'd want the type so that I can:

  1. Know the singleton store to access (if I need to full model)
  2. Toggle/display to proper icon in the tree
  3. Handle the leaf/childless cases (such as for Country)
One challenge in this case is I would need to ensure the AlbumStore, CountryStore and StampCollectionStore's are loaded prior to initializing/loading the tree store. As well, I would need to make sure the JSON output from the Album and StampCollection Restful Web Services includes an array of the ids of their children. (It actually is doing this today - but I bring this up as a point that others might consider). For example, an Album JSON String might look like the following:

{"total":1,"albums":[{"name":"Australia","id":154,"countries":[1104,733,3851,704]}]}
In the above example, the album for Australia contains references to the countries 1104, 733 etc. If the StampTreeDataProxy came across a parent BaseModel which was an Album reference, I could obtain the concrete Album from the AlbumStore to get the Country references and thus create an output list for the Callback.onSuccess() method.

While this above approach will take me more time to implement, I think it will provide one major performance improvement. The tree building time is strictly driven by tree AJAX calls to obtain the concrete Country, Album and Stamp Collection data models, as opposed to "N" calls for each node in the tree. It would also give a consolidated view of the tree and allow management of the models to be controlled by their respective stores in a more uniform fashion.

Finally, using a ReferenceModel approach, I actually do not even need the DataProxy. I'd have to test this out, but it looks like I can simply use the TreeBinder to bind a Tree to a TreeModel, and the TreeModel can be built programatically once the singleton data stores are created.

Monday, December 1, 2008

GXT Tree and Loading with REST

Anyone who has used GXT can tell you that it is very powerful, but examples (and the javadoc) are still few and hard to find. The examples that come with the SDK are great (see Ext GWT 1.2 Samples), but if you need to something beyond these you typically need to figure it out on your own. I hope to be able to write a series of blog articles on some of the designs I have figured out for the conversion of my Stamp application from Swing to GXT. (Some of my readers may remember I was experimenting with EXT-JS (more on this in a future blog)).
Getting a tree to work in GXT is not too hard. In fact the demos do a pretty job of illustrating this. However, my tree was a little more complex than the trees shown. First some features of my tree:
  1. Has three separate types of objects (Stamp Collections, Albums and Countries)
  2. Stamp Collections and Albums may contain children (Albums and Countries respectfully)
  3. A Restful WebService is used to return the content of a particular item. (for example to get the albums of a stamp collection with id of "5" the URI might look like http://hostname/web-app/resources/collections/albums/5).

My original approach was that I wanted to make a request on the selected tree node when an expansion occured. However when I used filtering, this caused the invocation of the filter to actually load all the nodes (which was very slow on the first filter). You can turn this off, however that defeated the point of the filter. It also didn't seem like the right solution since at least in the examples the trees were being loaded via an RPC call. So the problem really boiled down to making the tree make an AJAX call to the Restful Web Service upon requesting the expansion of an unloaded node. First I should mention that trees in GXT actually act on BaseModel objects. One point of interest here is that BaseModel object are not TreeModel's (in this way it does work differently than Swing-based trees).
The approach I took was to create a customized DataProxy and pass this proxy to the store on creation. Thus as nodes are loaded/expanded the data proxy will be used to obtain the node's children. My proxy essentially overloaded the load( ) method and for convienence added another method getRequestBuilder( ). I would send requests using the output of the request builder to the Restful Web Service and process the results with a JSONReader and output the load result in the callback's onSuccess( ) method. So lets look at some code:
public class StampTreeDataProxy implements DataProxy<BaseModel,List<BaseModel>> {

   public StampTreeDataProxy( ) {
      super();
   }

   /**  
    * Generate the request builder for the object. If it is the root node (ie. null)
    * then request the stamp collections.  Else, get the appropriate Albums or Countries.
    * For any other type of object, since we do not know how to build a Restful Web
    * URI, simply return a null value.
    * 
    * @param item   The current (parent) item.
    */
   protected RequestBuilder getRequestBuilder( BaseModel item ) {
      RequestBuilder builder = null;
      if( item == null ) {
 builder = HttpUtils.getRestfulRequest(StampModelTypeHelper.getResourceName(StampCollection.class));
      } else if( item instanceof NamedBaseTreeModel ){
         String pathInfo = ((item instanceof Album) ?
                StampModelTypeHelper.getResourceName(Country.class) :    
                StampModelTypeHelper.getResourceName(Album.class)) +"/" + ((NamedBaseTreeModel)item).getId();
         builder = HttpUtils.getRestfulRequest(
                StampModelTypeHelper.getResourceName(item.getClass()), pathInfo, HttpMethod.GET );
      }
      return builder;
   }
  
   public void load(DataReader<BaseModel, List<BaseModel>> reader, 
       final BaseModel parent, final AsyncCallback<List<BaseModel>> callback) {
      
      RequestBuilder builder = getRequestBuilder( parent );
      // If the builder is null, then we do not have a Restful Builder we can handle.
      if( builder == null ) {
         callback.onSuccess(new ArrayList<BaseModel>());
         return;
      }
      builder.setCallback(new RequestCallback() {
      
         public void onError(Request request, Throwable exception) {
            GWT.log("error",exception);
         }

      @SuppressWarnings("unchecked")
      public void onResponseReceived(Request request, Response response) {
         if( response.getStatusCode() == Response.SC_OK ) {
            if (HttpUtils.isJsonMimeType(response.getHeader(HttpUtils.HEADER_CONTENT_TYPE))) {
               JSONValue json = JSONParser.parse(response.getText());
               Class _c = StampCollection.class;
               if( parent instanceof StampCollection || parent instanceof Album) {
                  _c = ( parent instanceof StampCollection ) ? Album.class : Country.class;
               }

               // Modified JsonReader which can read structures of ModelType definitions.
               // For this, I believe a regular JsonReader would work, however
               // it would create instances of BaseModel objects instead of
               // my specific types (which have nice accessor methods).
               StructuredJsonReader<BaseListLoadConfig> reader = new
               StructuredJsonReader<BaseListLoadConfig>(new StampModelTypeHelper(), _c );
               ListLoadResult lr = reader.read(new BaseListLoadConfig(), json.toString());
               callback.onSuccess(lr.getData());
            }
            } else {
               GWT.log("a non-status code of OK" + response.getStatusCode(), null);
            }
         }
      });
      try {
         builder.send();
      } catch (RequestException e) {
         e.printStackTrace();
      }
   }
}

Using this DataProxy, I can create an instance and pass it to the store I am creating for the tree:
public class BrowseStore extends TreeStore<BaseModel> {
  
   public BrowseStore( ) {
      super(new BaseTreeLoader<BaseModel>( new StampTreeDataProxy()));
   }
 
   // Simplified method to create the tree content and load it 
   public void load( ) {
      removeAll();
      getLoader().load();
   }
  
}

In this desgin, when load() is called (either on the loader of the store or the store itself), the DataProxy will be called for the root element. Since I am ignoring the reader (argument to the load( ) method) I create the new StructuredJsonReader. As stated above, the reason for this is three fold:
  1. It handles structured ModelTypes (ie. a ModelType with a field representing another ModelType.
  2. The ability to delegate the creation of the BaseModel to another class (in this case the StampModelTypeHelper which will create the appropriate instance of the modeled object (eg. Country).
  3. Finally uses the class provided to create the propert model type definition.

There is one negative of this solution. Currently it is posting a single request per node in the tree (eg. given an Album get all the countries). I plan on redesigning this to be a little more efficient, however given the mixture of BaseModel types, in order to get an efficient structure downloaded, I may need to forgoe the clean model structure in preference for a more efficient algorithm.