Saturday, February 25, 2006

More Spring in Chains

Last week, I wrote about the Spring based Chained Controller to chain various controllers into a single request. This week, I provide some examples of its usage, and describe some modifications I made to it so as to make it easier to use.

Using a ThreadLocal to store shared context

In my original post, I had suggested using a the Spring BeanFactory as a ThreadLocal to store context between two controllers in a sequential chain so they can communicate. However, thinking about it some more, I figured that it would be friendlier to just provide a per-thread ChainedController context, so thats what I did. This involves a small modification in the original ChainedController code. We create a ThreadLocal as a private static member variable of the ChainedController, and provide public getters and setters. The variable declaration is shown below, the getContext() and setContext() methods follow the standard pattern:

1
2
3
4
5
private static ThreadLocal<Map<String,Object>> context = new ThreadLocal<Map<String,Object>>() {
    protected synchronized Map<String,Object> initialValue() {
        return new HashMap<String,Object>();
    }
};

To use it between two controllers A and B in a chain, A would set a variable into the context and B would retrieve it later and perform some decision based on the content of the variable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class A implements Controller {
    public ModelAndView handleRequest(...) {
        ...
        Map<String,Object> chainCtx = ChainedController.getContext();
        chainCtx.put("someVariable", "someValue");
        ChainedController.setContext(chainCtx);
    }
}

public class B implements Controller {
    public ModelAndView handleRequest(...) {
        ...
        Map<String,Object> chainCtx = ChainedController.getContext();
        Object someVariable = chainCtx.get("someVariable");
        // do something with someVariable
        ...
    }
}

Obviously, the context is only meaningful when used in a sequential chain. By definition, controllers in a parallel chain should have no dependencies on each other, which implies that parallel chains can have no shared context.

Example of Extending a ChainedController

I had mentioned the possibility of extending a ChainedController to plug in any special processing that needs to be done, but in retrospect, I think it may just be better to extend the chain with another controller which contains the necessary logic to do this. However, here is an (admittedly contrived) example, in case you really need to do this. The example computes and prints the response time at the bottom of the generated page. To solve this problem using configuration alone, you will need to add two controllers to the chain, one to capture the start time, and another to capture the end time and compute the difference. Extending is much simpler in this case, as shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class ResponseTimeReportingChainedController extends ChainedController {
                                                                                
    public ResponseTimeReportingChainedController() {
        super();
    }
                                                                                
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        StopWatch watch = new StopWatch();
        watch.start("response time");
        ModelAndView mav = super.handleRequest(request, response);
        watch.stop();
        mav.addObject("responseTime", watch.getTotalTimeMillis());
        return mav;
    }
}

The change to configure this controller instead of a ChainedController is to simply replace the class attribute for the controller in the Spring BeanFactory.

Bug in CallableController

I also discovered a bug in the CallableController inner class. The constructor signature took the HttpServletRequest and HttpServletResponse, but I was not passing them in. I did not notice the bug because my test case did not have to use the request for anything, but when I wrote the example of extending the ChainedController, I did, which exposed the problem. So the constructor for the CallableController will change to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private class CallableController implements Callable<ModelAndView> {
    ...
    public CallableController(Controller controller, HttpServletRequest request, HttpServletResponse response) {
        this.controller = controller;
        // the following two lines need to be added.
        this.request = request;
        this.response = response;
    }
    ...
}

Son of Spring in Chains - the DesignView

Sorry about the cheesy subtitle, but in a sense it is appropriate. Let me explain. The example web application I wrote to test the ChainedController uses JSTL on the front-end. Each controller in the chain is associated with one (or more, but at least one) JSP file. Thus the controller + JSP can be thought of as a single component. The components are brought together on the web page by plugging them into a Tiles layout.

For most web applications (at least the ones I am familiar with), the Java/JSP developer does not write the HTML that the user sees on his browser. A graphic artist (usually a HTML/CSS guru) builds a mockup with data that goes "Lorem Ipsum..." and gives it to the web developer. The web developer then works backward to fill out the "Lorem Ipsum..." stuff with valid data from a real data source. I feel this process is backward, since it is not round-trip. If there is a change, the HTML designer is generally not able to tweak the JSP to reflect that. So the web developer is stuck merging presentation changes from the new mockup into the JSP, which is more difficult and time-consuming than if the presentation changes were directly applied to the JSP.

One team I know actually reversed this flow, and they used Velocity as their view layer. Since Velocity syntax is simpler, the HTML writer was able to double as a Velocity coder as well. What the Java developer provided was full documentation of the bean(s) available in the page context. Since JSTL provides a similar bean abstraction (by means of a dotted notation), the DesignView is an attempt to provide, via reflection, a table of key-value pairs of all variables (and their values, based on test data) available to the JSTL coder in the page context. Here is the code for the DesignView:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package org.springchains.framework.views;
 
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.View;
 
/**
 * A simple view implementation that renders the contents of the Model as
 * a tabular display of key-value pairs. The keys are displayed as JSTL
 * variables, which can provide information to the HTML designer.
 * @author Sujit Pal
 * @version $Revision: 1.1 $
 */
public class DesignView implements View {
 
    public DesignView() {
        super();
    }
     
    /**
     * The View renderer.
     * @param model the Model from the ModelAndView object generated by the
     * Controller.
     * @param request the HttpServletRequest object.
     * @param response the HttpServletResponse object.
     * @throws Exception if one is thrown.
     */
    @SuppressWarnings("unchecked")
    public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        List<Map<String,String>> keyValuePairList = new ArrayList<Map<String,String>>();
        Set<String> keys = model.keySet();
        for (String key : keys) {
            Object obj = model.get(key);
            Map<String,String> keyValuePairs = new LinkedHashMap<String,String>();
            keyValuePairs.put(key, obj.getClass().getName());
            renderBeanAsNameValuePairs(keyValuePairs, obj, key);
            keyValuePairList.add(keyValuePairs);
        }
        PrintWriter writer = response.getWriter();
        String htmlOutput = renderHtmlFromKeyValuePairList(keyValuePairList);
        response.setContentLength(htmlOutput.length());
        response.setContentType("text/html");
        writer.print(htmlOutput);
        writer.flush();
        writer.close();
    }
 
    /**
     * Traverses the bean graph for the bean specified by obj, and populates
     * a Map of keyValue pairs. This is a recursive function, which will be
     * called on dependent objects for the top-level bean. Recursion stops when
     * the Object encountered is "printable" as reported by isPrintable().
     * @param keyValuePairs the Map of key-value pairs to populate.
     * @param obj the object to render.
     * @param prefix the prefix (the top level call is the key name), and any
     * subsequent recursive calls adds extra parts to the prefix.
     * @throws Exception if one is thrown.
     */
    @SuppressWarnings("unchecked")
    private void renderBeanAsNameValuePairs(Map<String,String> keyValuePairs, Object obj, String prefix) throws Exception {
        Map<String,Object> properties = PropertyUtils.describe(obj);
        for (String propertyName : properties.keySet()) {
            if ("class".equals(propertyName)) {
                continue;
            }
            Object property = properties.get(propertyName);
            if (property.getClass().isPrimitive()) {
                // the property is a primitive, such as int, long, double, etc.
                keyValuePairs.put(prefix + "." + propertyName, String.valueOf(property));
            } else if (isPrintableObject(property)) {
                // the property is a printable, see isPrintable()
                keyValuePairs.put(prefix + "." + propertyName, StringUtils.defaultString(property.toString(), "NULL"));
            } else if (property instanceof Collection) {
                // the property is a collection, iterate and recurse
                Collection collectionProperty = (Collection) property;
                keyValuePairs.put(prefix + "." + propertyName + ".size()", String.valueOf(collectionProperty.size()));
                int i = 0;
                for (Iterator it = collectionProperty.iterator(); it.hasNext();) {
                    Object indexedProperty = it.next();
                    renderBeanAsNameValuePairs(keyValuePairs, indexedProperty, prefix + "." + propertyName + "[" + i + "]");
                    i++;
                }
            } else {
                // recurse down the object graph
                renderBeanAsNameValuePairs(keyValuePairs, property, prefix + "." + propertyName);
            }
        }
    }
 
    /**
     * Returns true if the object is a printable object, ie, a toString()
     * on the object will return a descriptive idea of the contents. These
     * include all the base classes in java.lang (String, Integer, etc)
     * and some others such as java.util.Date and java.math.BigDecimal.
     * This list is subject to change as we encounter other classes which
     * can be considered printable for the purpose of our DesignView.
     * @param obj the Object to test.
     * @return true or false.
     */
    private boolean isPrintableObject(Object obj) {
        if (obj == null) {
            return true;
        }
        String className = obj.getClass().getName();
        if (className.startsWith("java.lang.")) {
            return true;
        } else if ("java.util.Date".equals(className)) {
            return true;
        } else if ("java.math.BigDecimal".equals(className)) {
            return true;
        }
        return false;
    }
 
    /**
     * Generates a standard HTML document (with a gray screen) with an
     * embedded table showing the variables available to the HTML designer
     * to embed dynamic content into the page.
     * @param keyValuePairList a List of Maps, each Map corresponds to a single
     * Model key.
     * @return a String which can be pushed into the response.
     */
    private String renderHtmlFromKeyValuePairList(List<Map<String,String>> keyValuePairList) {
        StringBuffer buf = new StringBuffer();
        buf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">").
            append("<html>").
            append("<head>").
            append("<title>Design View</title>").
            append("<body>").
            append("<table cellspacing=\"3\" cellpadding=\"3\" border=\"1\">");
        for (Map<String,String> keyValuePair : keyValuePairList) {
            int i = 0;
            for (String key : keyValuePair.keySet()) {
                if (i == 0) {
                    // header, blue background
                    buf.append("<tr bgcolor=\"blue\" fgcolor=\"white\">").                        append("<td><b>").
                        append(StringEscapeUtils.escapeHtml(key)).
                        append("</b></td>").
                        append("<td>").
                        append(StringEscapeUtils.escapeHtml(keyValuePair.get(key))).
                        append("</td>").
                        append("</tr>");
                } else if (key.endsWith(".size()")) {
                    // collection, make light gray
                    buf.append("<tr bgcolor=\"gray\">").
                        append("<td><b>").
                        append(StringEscapeUtils.escapeHtml(key)).
                        append("</b></td>").
                        append("<td>").
                        append(StringEscapeUtils.escapeHtml(keyValuePair.get(key))).
                        append("</td>").
                        append("</tr>");
                } else {
                    // not header, white background
                    buf.append("<tr>").
                        append("<td><b>${").
                        append(StringEscapeUtils.escapeHtml(key)).
                        append("}</b></td>").
                        append("<td>").
                        append(StringEscapeUtils.escapeHtml(keyValuePair.get(key))).
                        append("</td>").
                        append("</tr>");
                }
                i++;
            }
        }
        buf.append("</table>").
            append("</body>").
            append("</head>").
            append("</html>");
        return buf.toString();
    }
 
}

Yes, I know that the rendering is done in Java instead of using a template, and that its harder to maintain. However, this is unlikely to be modified very frequently, so its probably ok to not use an external template to do this.

Each component controller is configured with the DesignView by default. The JSTL coder will be able to change it to something else via configuration if desired. An example of how to set up the DesignView as the default is shown below:

1
2
3
4
5
6
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mav = new ModelAndView();
        // do something with the model
        ...
        mav.setView(new DesignView());
        return mav;

A screenshot of the DesignView of the central component (MainBody) is shown below on the left, and the actual page is shown on below on the right. The DesignView specifies the name of the bean that has to be bound using a jsp:useBean in blue. All other rows refer to JSTL variables available to the JSTL programmer.

Saturday, February 18, 2006

Spring in Chains

A long time ago, before Spring, and even before Struts and Webwork, I worked with a home-grown proprietary web development framework which was based on the Chain of Responsibility pattern. What it did was allow the developer to write small modules and chain them together to handle the request. When the Controller encountered the first module, it would instantiate a Context object and stick it into the Request. Each module would interact with the Context, sometimes adding fresh data and sometimes using data that modules earlier in the chain had written into it. The framework had its warts, but it was very easy to add new functionality to web pages - simply stick a new module into the chain that generates the data you need and add the presentation for the data on the view JSP.

I have been working with Spring for about 2 projects now, and while I think its MVC implementation is by far simpler and more extensible than Struts or Webwork (the other two frameworks I know), I did not find the functionality I described above, so I set about writing one. This article describes the main features of the ChainedController, includes the code, and explains how to set up some typical chaining configurations.

I originally thought that I could use the Apache Commons Chains project to implement the additional functionality, but a quick look through its feature set indicated that it would not work for me. Specifically, Apache Commons Chains has as its unit of chaining a Task object, which would be incompatible with my unit of chaining, a Controller object. Secondly, I wanted to be able to chain tasks to execute in parallel as well as sequentially, while Chains provide for sequential chains only.

My ChainedController implements the Spring Controller interface. The Controller itself takes as a parameter a List of other Controllers it should chain. The second parameter it takes is the chaining policy - can be either parallel or sequential (the default if not specified). The third parameter is the view name the Chained controller should forward to.

The code for the ChainedController is here:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package org.springchains.framework.controllers;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/**
* ChainsController uses the Chain of Responsibility Pattern to delegate
* control to a chain of Controller objects that are passed into it at
* configuration time. A ChainsController also takes an additional
* policy setting that decides whether it should delegate to the
* Controller chain in a serial or parallel manner. A ChainsController
* is itself a Controller, which means that a ChainsController can be
* passed in as a parameter to another ChainsController. This allows for
* very fine-grained control over the entire controller chain.
* @author Sujit Pal
* @version $Revision$
*/
public class ChainedController implements Controller {
  
   private List<Controller> controllers;
   private boolean parallel = false;
   private String viewName;
  
   private class CallableController implements Callable<ModelAndView> {
       private Controller controller;
       private HttpServletRequest request;
       private HttpServletResponse response;
      
       public CallableController(Controller controller, HttpServletRequest request, HttpServletResponse response) {
           this.controller = controller;
       }
      
       public ModelAndView call() throws Exception {
           return controller.handleRequest(request, response);
       }
   };
  
   /**
    * Set a List of Controller objects that must be chained.
    * @param controllers a List of Controllers.
    */
   public void setControllerChain(List<Controller> controllers) {
       this.controllers = controllers;
   }
  
   /**
    * Set the policy using which the Controller objects must be invoked.
    * By default, parallel is set to false, which means that the
    * chain of Controllers will be called serially. If set to true, the
    * ChainedController will spawn a thread for each of the Controllers in
    * the chain, and execute all threads simultaneously. In either case,
    * an exception thrown by one of the Controllers in the chain will
    * result in an exception thrown by the ChainsController itself.
    * @param parallel if true, serial if false.
    */
   public void setParallel(boolean parallel) {
       this.parallel = parallel;
   }
  
   /**
    * Allows declarative setting of the final view name. This may also
    * be programatically defined by subclassing the ChainedController.
    * @param viewName the name of the view the controller will forward to.
    */
   public void setViewName(String viewName) {
       this.viewName = viewName;
   }
  
   /**
    * Handles the request and builds a ModelAndView object to forward to the
    * view layer. Delegates to the private handleRequestXXX() methods based
    * on whether parallel is set to true or false.
    * @param request the HttpServletRequest object.
    * @param response the HttpServletResponse object.
    * @return a ModelAndView object.
    * @exception if one is thrown by the controllers in the chain.
    */
   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
       return (parallel ? handleRequestParallely(request, response) : handleRequestSequentially(request, response));
   }
  
   /**
    * Spawns multiple threads, one for each controller in the list of
    * controllers, and within each thread, delegates to the controller's
    * handleRequest() method. Once all the threads are complete, the
    * ModelAndView objects returned from each of the handleRequest()
    * methods are merged into a single view. The view name for the model
    * is set to the specified view name. If an exception is thrown by
    * any of the controllers in the chain, this exception is propagated
    * up from the handleRequest() method of the ChainedController.
    * @param request the HttpServletRequest object.
    * @param response the HttpServletResponse object.
    * @return a merged ModelAndView object.
    * @throws Exception if one is thrown from the controllers in the chain.
    */
   @SuppressWarnings("unchecked")
   private ModelAndView handleRequestParallely(HttpServletRequest request, HttpServletResponse response) throws Exception {
       ExecutorService service = Executors.newCachedThreadPool();
       int numberOfControllers = controllers.size();
       CallableController[] callables = new CallableController[numberOfControllers];
       Future<ModelAndView>[] futures = new Future[numberOfControllers];
       for (int i = 0; i < numberOfControllers; i++) {
            callables[i] = new CallableController(controllers.get(i), request, response);
            futures[i] = service.submit(callables[i]);
       }
       ModelAndView mergedModel = new ModelAndView();
       int i = 0;
       for (Future<ModelAndView> future : futures) {
           try {
               ModelAndView model = future.get();
               if (model != null) {
                   mergedModel.addAllObjects(model.getModel());
               }
               i++;
           } catch (ExecutionException e) {
               throw new Exception(
                   "Controller: " + controllers.get(i).getClass().getName() +
                   " threw exception: " + e.getMessage(), e);
           }
       }
       if (StringUtils.isNotEmpty(this.viewName)) {
           mergedModel.setViewName(this.viewName);
       }
       return mergedModel;
   }
  
   /**
    * Calls the handleRequest controller for each of the Controllers in the
    * chain sequentially, merging the ModelAndView objects returned after each
    * call and returning the merged ModelAndView object. An exception thrown
    * by any of the controllers in the chain will propagate upwards through
    * the handleRequest() method of the ChainedController. The ChainedController
    * itself does not support any communication between the controllers in the
    * chain, but this can be effected by the controllers posting to a common
    * accessible object such as the ApplicationContext. Note that this will
    * introduce coupling between the controllers and will be difficult to
    * arrange into a parallel chain. A controller can stop processing of the
    * chain by returning a null ModelAndView object.
    * @param request the HttpServletRequest object.
    * @param response the HttpServletResponse object.
    * @return the merged ModelAndView object for all the controllers.
    * @throws Exception if one is thrown by one of the controllers in the chain.
    */
   public ModelAndView handleRequestSequentially(HttpServletRequest request, HttpServletResponse response) throws Exception {
       ModelAndView mergedModel = new ModelAndView();
       for (Controller controller : controllers) {
           try {
               ModelAndView model = controller.handleRequest(request, response);
               if (model == null) {
                   // chain will stop if a controller returns a null
                   // ModelAndView object.
                   break;
               }
               mergedModel.addAllObjects(model.getModel());
           } catch (Exception e) {
               throw new Exception(
                   "Controller: " + controller.getClass().getName() +
                   " threw exception: " + e.getMessage(), e);
           }
       }
       if (StringUtils.isNotEmpty(this.viewName)) {
           mergedModel.setViewName(this.viewName);
       }
       return mergedModel;
   }
}

The chaining policy and the magic of Spring declarative configuration allows the ChainedController to be chained in a variety of interesting ways. To test my implementation, I set up a web application to display a page containing 5 tiles - the Header, Footer, Left and Right sidebars and the Main Body. The examples show the ChainedController configuration to implement a sequential, parallel and hybrid chain.

A sequential configuration looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
   <bean id="sequentialChainedController" class="org.springchains.framework.controllers.ChainedController">
       <property name="controllerChain">
           <list>
               <ref bean="headerController" />
               <ref bean="leftSidebarController" />
               <ref bean="rightSidebarController" />
               <ref bean="mainBodyController" />
               <ref bean="footerController" />
           </list>
       </property>
       <property name="parallel" value="false" />
       <property name="viewName" value="main" />
   </bean>

A parallel configuration with the same set of controllers would look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
   <bean id="parallelChainedController" class="org.springchains.framework.controllers.ChainedController">
       <property name="controllerChain">
           <list>
               <ref bean="headerController" />
               <ref bean="leftSidebarController" />
               <ref bean="rightSidebarController" />
               <ref bean="mainBodyController" />
               <ref bean="footerController" />
           </list>
       </property>
       <property name="parallel" value="true" />
       <property name="viewName" value="main" />
   </bean>

Spring also allows you to refer to other bean collections using a reference. Using this feature, one can set up arbitarily complex chains, which are hybrids of sequential and parallel chains. Obviously, you would want to think about these chains when you build them, to make sure they make sense. Here is an example, based on the assumption that the mainBody component will take about the same time to generate and render compared to all the other components put together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   <bean id="nonMainBodyChainedController" class="org.springchains.framework.controllers.ChainedController">
       <property name="controllerChain">
           <list>
               <ref bean="headerController" />
               <ref bean="leftSidebarController" />
               <ref bean="rightSidebarController" />
               <ref bean="footerController" />
           </list>
       </property>
       <property name="parallel" value="false" />
       <property name="viewName" value="main" />
   </bean>
                                                                             
   <bean id="hybridChainedController" class="org.springchains.framework.controllers.ChainedController">
       <property name="controllerChain">
           <list>
               <ref bean="nonMainBodyChainedController" />
               <ref bean="mainBodyController" />
           </list>
       </property>
       <property name="parallel" value="true" />
       <property name="viewName" value="main" />
   </bean>

Sometimes it is necessary to decide, based on the contents of the model, which view to forward to. The default ChainedController would not help in this case. You may have to subclass the ChainedController, and make the decision in the handleRequest method, something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public ModelAndView handleRequest(
       HttpServletRequest request, HttpServletResponse response)
       throws Exception {
   ModelAndView mav = super.handleRequest(request, response);
   if (mav.getModel().get("somedata") != null) {
       mav.setViewName("view1");
   } else {
       mav.setViewName("view2");
   }
   return mav;
}

Why would one use chaining? Well, the answer depends on what you are using to achieve the same functionality currently. If you are using interceptors to put logic before and after your controller logic, then I think that being able to chain your controllers is going to have better performance, since interceptors use AOP, and AOP is quite a performance hog. If you write all your code in a monolithic controller, depending on a service layer to delegate the logic within your controller for each page component, you would still need to add or remove code when components are added or removed from your page. With chained controllers, adding or removing a component involves only configuration changes.

Another advantage to using chaining, especially if you have a Tile or Tile-like setup on your front end, where a page is broken up into distinct components, is the componentization it allows. Tiles already componentizes the page, but chaining allows you to link a single controller with a tile. This controller + tile combo can be dropped into another page without having to write a line of Java code.

Some caveats, though. Unlike the framework from which the ChainedController is inspired by, there is no built-in way for controllers in a chain to communicate via a Context object. For parallel chains, it would not make much sense anyway, since by definition, controllers chained parallely cannot depend on each other. For sequential chains, however, it should be fairly easy to establish a ThreadLocal reference to a specific bean in the ApplicationContext.

Another difference is that the original framework did not allow for parallel chaining. I thought that it may be useful for generating extremely content heavy or portal style pages, where a bunch of functionally unrelated components are thrown together on a single page because it makes sense to have them on the same page from a user's point of view. Generation of these components could happen in parallel, since they dont depend on each other functionally, and be presented to the user quicker. Of course, there is a price to pay - each request generates multiple threads, equal to the number of controllers in the parallel chain, instead of a single thread per request. Of course, the threads will terminate faster (or so we hope). I haven't tried the ChainedController implementation under load, so I do not know how this would scale to heavy traffic.

Finally, I am a little surprised that no one has done this before. It is possible that people have thought of it, and think its a bad idea. If you think this is a bad idea, please do let me know.

During this exercise, I took the opportunity to learn JSTL by doing all my JSPs using JSTL. Here are some links I used for this, hopefully they will be useful to others trying to learn JSTL as well.

Saturday, February 11, 2006

WebApps Development the Rails way

When people think of Web application developers, presumably the first image they have is a graphic designer with some PHP or other scripting knowledge. While this was true 5-7 years ago, the web application development landscape has changed dramatically in recent years. I can only speak for Java based web development, but I am sure that there is a similar proliferation of choices in other languages too. Today's Java web developer needs to know object relational mapping (ORM) technologies such as EJB, Hibernate or JDO, web application frameworks such as Struts, Webwork or Spring, front end tools such as HTML, JSP, Velocity, Tapestry, JSTL, Java Server Faces or even Javascript with AJAX, with miscellaneous middleware components thrown in such as Lucene for search, JMS for asynchronous messaging, SOAP for remoting, and so on. It a wonder that a web developer doesn't get more respect.

However, I find that the biggest challenge in web development is not what technologies to use (although that choice does have impact on scaling, responsiveness and other usability concerns), but what the site's navigation should look like. Most sites attempt to layer logic over HTTP's stateless protocol to provide an illusion of continuity to the end-user. Figuring out the appropriate navigation strategy plays a big part in creating that illusion. Of course, it helps if the web developer can think like his end customer, such as a developer for an online games site who is himself a gamer. Some companies are not so lucky, such as a bank, whose developers are typically more financially savvy than the end users they serve.

It is easy to get bogged down by details of the navigation, and in the process, create web site flow logic that is difficult to maintain. Worse, the flow logic leaks into the application logic, making core application code that much more brittle.

I have been reading about Ruby on Rails in the Agile Web Development with Rails book. In short, what the Rails framework does is allow you to start with a database table and automatically generate your model, controller and view with rails scripts. This allows you to set up simple web based applications relatively painlessly. To make any significant customizations to the generated application, however, you would need to know Ruby, which I do not as yet. There are other efforts to replicate this functionality in the Java world, such as the Trails project and Appfuse (with AppGen). However, what excited me about Rails is not the code generation itself, but that it represents a simplified way to do web development.

Most web applications have as their basis one or more databases, with tables representing the business entities for the application. The Rails approach to web development allows you to build the controllers and views to store, retrieve or delete these entities first. Once this is done, you can then concentrate on building up the relationships between the entities by providing the appropriate navigation hooks.

For example, in a purchasing system, you would first build up the operations for the Order and Part entities, including the views for each. The Part entity would be a one-to-many relationship with Order, so in your database you would represent it with a foreign key in Part pointing back to Order.

1
2
3
4
5
6
create table Order (...);
create table Part (
   id int not null primary key,
   ...
   order_id int not null foreign key(Order)
);

In your model, you would represent this with a collection member variable, like so:

1
2
3
4
5
6
class Order {
   private Set<Part> parts;
   ...
   public Set<Part> getParts() { get parts; }
   public void setParts(Set<Part> parts) { this.parts = parts; }
}

So even before you attempted to build the view for the Purchase order which contains a set of Part line items for the Order entity, you would have the model, view and controller for the Part and Order entities ready.

Navigation would then be grafted on to the system at the entity level. For example, the Payment entity would be linked to the purchase order view by a navigation link such as "Pay for this order". At the database level it would be represented as a one-to-one mapping between Order and Payment.

1
2
3
4
5
6
7
8
create table Order (
   ...
   payment_id int not null foreign key (Part)
);
create table Payment (
   ...
   order_id int not null foreign key (Order)
);

The model for Payment and Order will look like this, with properties that point back to each other.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Payment {
   private Order order;
   ...
   public Order getOrder() { return order; }
   public void setOrder(Order order) { this.order = order; }
}
class Order {
   private Payment payment;
   ...
   public Payment getPayment() { return payment; }
   public void setPayment(Payment payment) { this.payment = payment; }
}

Effectively, you are letting the database model for the application drive the navigation, which is probably not a bad thing for a number of reasons.

First, by designing the database layer first, its very likely that the database model has a strong correlation with the business domain model. The domain model represents the aggregate of the way people have been doing this particular thing for a number of years, so inefficiencies have presumably been weeded out of the process. In some industries, domain models are standardized, so new developers joining your team are likely to be familiar with the domain model even before they sign up. Even if the domain model is not standard, it is easier to explain a domain model to customers and new hires than arcane data models. As for explaining navigation flows, I have found that it is most often never explained, much less justified with logic; new developers are left to navigate through the site themselves and find out on their own.

Second, the user is more likely to be familiar with the domain model for your business than navigation that you as a developer or designer can dream up, since you are more likely than not to be out of sync with your customer's needs, unless you are in the happy position of being able to think exactly like your customer. Thus the user is going to be more comfortable with a navigation strategy that is based on the domain model. Users who migrate to your application from other applications are also likely to have less of a re-learning curve, since the other application would have implemented at least part of its navigation to be domain-driven.

Third, and finally, it allows you to defer development of navigation details until you are done developing the core functionality for the business entities. From an agile viewpoint, this allows you to demonstrate working code to your client sooner than if you try to develop code with the navigation logic in place, and possibly mixed in with, the core logic for the entities. During the phase where you are grafting your navigation into your partially completed web application, you are really working with larger working components (the entities) and treating them like black boxes as you wire them together with your navigation strategy, so it also results in robust, easy to understand, and easy to maintain code.

I have recently been experimenting with this strategy on a open-source development project where the mandate was to develop a web application to front a model layer developed with Hibernate by members of the core team. I decided to use Tapestry to do the development (I did not know Tapestry and this was a chance to learn). The core entities are now ready and I have submitted it back to the core team for their feedback. The great thing about this strategy so far is that I did not have to have a complete understanding of the domain which the model is designed for. I can defer this work till the next stage, when I apply the navigation and refine the view layer based on the core team's feedback. The feedback itself will provide me with most of the understanding of the domain model that is needed.