///Use Continuations to Develop Complex Web Applications

Use Continuations to Develop Complex Web Applications

A Programming Paradigm to Simplify MVC for the Web

If you’ve ever developed a non-trivial Web application, you know that development complexity is increased by the fact that Web browsers allow users to follow arbitrary navigation paths through the application. No matter where the user navigates, the onus is on you, the developer, to keep track of the possible interactions and ensure that your application works correctly. While the traditional MVC approach does allow you to handle these cases, there are other options available to help resolve application complexity. Developer and frequent developerWorks contributor Abhijit Belapurkar walks you through a continuations-based alternative that could simplify your Web application development efforts.

Due to the inherent stateless nature of HTTP, Web technologies suffer from the problem of state information being forgotten between two successive user interactions. An interactive Web application consists of a collection of scripts wherein a single interaction comprises one script delivering a page to the browser (then ending), the user completing and submitting the form at some later point in time, and another (possibly different) script handling the submitted form. Thus, application logic is spread across a multitude of scripts.

Matters are further complicated by the fact that browsers allow users to backtrack in their interactions or clone an in-progress interaction and run both in parallel. Given this set of possibilities, a user can pursue multiple navigational paths within an application at any given time, and it’s up to you to write code to ensure that each outcome is successful. Web development frameworks, such as Spring and Struts, allow you to handle multiple navigational paths, but they do so at the cost of increasing the complexity of an already overly complex code base.

In this article, I’ll introduce a continuations-based alternative that can simplify the development of complex Web applications. I’ll start with an introduction to continuations, including an argument for how the continuations-based approach can be a shot in the arm for the traditional MVC style of programming. Then I’ll move on to a simple example: an enterprise application that demonstrates the advantages of using continuations in terms of ease of development and understanding of the application code. Because one of the chief disadvantages of using continuations is their lack of support on the Java platform, I’ll use the Apache Cocoon framework to demonstrate a JavaScript implementation of the example program and a pure Java language one. I’ll conclude with an overview of the pros and cons of using continuations.

Select the Codeicon at the top or bottom of this article to download the example application source code. See Resources to download the Apache Cocoon framework, which you will need to run the example.

What is a Continuation, Anyway?

A continuation is traditionally defined as a function representing “the rest of the computation” or “what to do next.” In other words, sending the intermediate result (generated by the preceding computation) to a continuation should yield the final result of the overall computation.

Consider, for example, the following rudimentary Java method that returns the square of the integer that is passed to it:

Listing 1. Method to compute square of integer input

public static int computeSquare(int x)

{
return (x*x);
}

This method returns a value, but leaves implicit the location to which the value should be returned. A continuation, properly applied, would make the return location explicit.

So, suppose I change the above method — and every other method in the system — to include an additional argument representing a continuation. Typically, this would be the last argument following all the other arguments to the method. When the function is called, it performs its internal logic as before; only, instead of returning the output value, it continues with the output value, by passing the value on to the continuation and asking the computation to resume. Thus, the above method would be rewritten as shown in Listing 2:

Listing 2. Method rewritten using a continuation object

public static int computeSquare(int x, Continuation c)

{
c.evaluate(x*x);
}

This programming style, in which no function is ever allowed to return, is called Continuation Passing Style, or CPS. A function, f1, emulates returning by passing its would-be return value to a continuation function that must have been explicitly passed to it. Similarly, if f1 needs to call a second function, f2, in the middle, it must pass to f2 (along with the rest of the arguments) a continuation representing the “rest of f1.” Once f2 is finished, the continuation “rest of f1” is resumed with the output of f2’s computation.

Now, to add a little twist, I’ll bring in another function, f3, which is called at the tail end of f2. If f2 were to follow f1’s suit in terms of passing its continuation, it would end up passing only the continuation of f1 to f3. Resuming the continuation of f1 would be all that would remain once f3 had been executed.

Said another way, a continuation is a saved snapshot of the executable state of a program at any given point in time. It is possible to restore this state and restart the execution of the program from that point onward, such that the stack trace, all the local variables, and the program counter can reclaim their old values. See Resources to learn more about continuations. Now I’ll focus on showing you what they can do to simplify your programming efforts in complex Web applications. Before we get into that, let me take a moment to further explain the problems I’m trying to address.

Higher order functions

Note that in the example of Listing 2, the function computeSquare is essentially a higher order function, because it takes a functor object (the Continuation object, c) as parameter and sends a message to the evaluate method of the functor with the intermediate result generated by computing x*x.

Problems in Conventional Web development

Model-View-Controller (MVC) is the universally adopted pattern for developing interactive applications, including those for the Web. This well-known pattern organizes an interactive application into three separate modules: one for the application model with its data representation and business logic, the second for views that provide data presentation and user input, and the third for a controller to dispatch requests and control flow.

So what is this “flow” that the controller manages? A typical Web application consists of a well-defined sequence of interactions with the user in terms of loading pages and awaiting the return of filled forms. In that sense, a Web application is like an event-driven state machine. This event model is what is realized in a typical MVC architecture via the controller.

For example, suppose that the user requests a certain page from the server, which contains a form to be filled. The user spends some time thinking about and filling in the answers, then submits the form. When this event arrives at the server (the controller module), the application moves into the next logical state, depending on the present state, the data submitted by the user, and the business logic. The outcome of this state transition, as visible to the user, is that the next page in the sequence or the earlier page, along with an error message, is displayed.

This cycle is repeated as the state machine is propelled on its path from the begin state to the end state, at which point the Web application is deemed to have fulfilled the functionality required in that specific use case. The state diagram that controls the various possible flows from a “begin” state to an “end” state can either be implicitly implemented in the controller module (typically a servlet) or, as in the case of some Web development frameworks, can be externalized as metadata in configuration files.

However the framework is implemented, the basic idea of a state machine is always there. A number of issues can pop up while developing Web applications based on this model, as described below:

• Depending on the size of the state machine and the amount of data required to maintain a client’s current state (as a Web application may be accessed by a large number of clients at any given time), the application logic could become unnecessarily cluttered and complex.

• The client may choose to hit the back button on the browser at any time during the sequence of state transitions, or may clone the browser window to initiate a parallel sequence of actions. Either move could lead to multiple (sometimes even concurrent) submissions corresponding to states that had already been passed in the original interaction. As a result, the application would be forced to keep track of every individual transaction and provide the correct response to each of them.

• A similar problem could arise in the case where a Web application was trying to gather information from the user in a series of forms spread over multiple pages. If the generation of a later form depended on the combination of responses the user had provided in the previous ones, then the application would be forced to keep track of the responses entered as part of each interaction and make sure that the correct page was returned in response to each one of them.

Model 2 Web development frameworks do, in general, provide custom techniques for mediating one or more of the above mentioned problems. However, none is as intuitive and easy to develop as the continuations-based alternative, which resolves all of these issues in one go.

Model 1 vs. Model 2 architectures

Strictly speaking, only MVC’s Model 2 architectures have a controller servlet. Model 1 architectures have decentralized control in which the browser directly interacts with servlets or JSP technology, which in turn access and work with the JavaBeans components representing the application model. In such architectures, the next page to display is determined by the user clicking a link on the current page or on the basis of parameters sent with a request. Most MVC-based Web applications today implement the Model 2 architecture.

About event-driven programming

The event-driven style of programming user interfaces dates back to the advent of client-server architectures. It is based around a central event processor and a number of event handlers that register with it. Each handler registers its interest in being notified when specific events occur. The user interaction state is maintained in the central module, which dispatches incoming events to the registered handlers depending on the current state held internally.

Most Web-based interactions are a special case of event-driven programming, wherein the interface display is delegated to a Web browser rather than managed by a thick-client executable running on the user’s workstation. Whereas a typical thick client would disallow user-driven functionality such as backward navigation and cloning, Web browsers support, and even encourage, it. Of course, resourceful programmers have found ways to customize the browser interface (using scripting code) to disallow such operations, but this introduces a dependency on the ever changing quirks of different browsers.

While MVC’s event-driven style of programming yields numerous advantages, it also leads to business functionality spread across multiple modules, making it fairly complex to develop, understand, and maintain any reasonably complex Web application. Even as numerous Web development frameworks (such as Struts, Spring, and JavaServer Faces) were developed to hide the complex plumbing beneath most MVC-style interfaces, some developers have begun to acknowledge the fact that other programming models deserve to be further explored.

The Case for Continuations

A continuations-based Web application neatly sidesteps the above-mentioned problems associated with Web application development. In contrast to MVC-based Web applications, a continuations-based application is written as a single monolithic program. Anytime the program requires input from the user, a Web page containing the relevant forms is sent back to the user’s browser, and a continuation representing the remaining part of the application logic is generated and put away. (I will soon explain your options for “putting away” the continuation.) Because it is important to be able to restart the remaining part of the application logic upon receiving the user’s response, a unique id is also generated to serve as the key to search for this particular continuation in the continuations repository. This id is also sent down, along with the page displayed to the user in an appropriate manner such that the form submission will cause the id to be sent back in the response.

It’s important to note that the continuations-based program behaves no differently from any other stand-alone program that uses blocking reads and writes. All the usual programming constructs can be used, including branching on if conditions, for and while looping, and more. The only difference is that in an MVC model, the application code using these constructs would be scattered across different modules and pages, but in the continuations-based program, the entire logic is contained within one program.

When the required response arrives from the user, the server, which is providing the continuations infrastructure for the deployed Web application, retrieves the continuation id from the response — along with the regular submitted data — and retrieves the continuation from the repository using this id. The continuation is then resumed (that is, the application logic starts executing starting from the line of code immediately following the line in which the continuation was created). Typically, the first few lines will extract the rest of the data submitted by the user, implying that the request object must be made available by the server within the program, and the business logic will continue with this received data.

Upon resuming a continuation, the code that starts executing may require more input data from the user, which will necessitate further interaction. This is handled by creating a second continuation, saving it in the continuations repository, and sending the form in which the appropriate continuation id is embedded to the user. The generated outer and inner continuations can be seen to form a tree structure with the former being a parent node and the latter a child node. Similarly, if the business logic has conditional code (different actions based on the outcome of an if clause) and both branches led to the formation of continuations corresponding to possibly different data-gathering pages being sent back to the browser, both these continuations will be the children of the outer continuation and siblings of each other. In this way, the complete application code can be seen to correspond to a tree of continuations — a forest of continuations.

Remember blocking I/O?

The approach described in this article uses continuations in a typical read-write program (or the equivalents of reads and writes in the language being used). To summarize: A call to a read causes the program to block and resume only after the user has provided an input. The provided input is used to continue the application logic. Any intermediate output is displayed to the user via a non-blocking write call. The next time the program requires user input, it makes another call to read and blocks again. This sequence continues until the program terminates.

User-Centered Navigation

From the outside, the continuations-based approach cannot be differentiated from an MVC architecture. The user is free to go back to any previously submitted Web page, change any data required, and resubmit the form via the browser. The difference is on the inside, in the amount of code-juggling required to get the navigation to work correctly. The continuations-based approach requires no additional effort for this, because a continuation id is always associated with each submitted page. The server just has to look up the correct continuation for a given submitted page and ask it to resume.

Further suppose that the user is viewing a page. In terms of the so-called tree of continuations, this page is “marked” by a certain “continuation node.” If the user hits the Back button once to return the page submitted previously, the marker in the continuations tree is moved up one level and set to point to the parent node of this node. This walk up the continuations tree happens each time the user hits Back. Now suppose the user stops on a certain page, re-enters data, which may or may not be the same as the data previously entered on this page, and resubmits the form. This causes the marker in the continuations tree to move down one level to point to a child node. However, because the application logic may have decided to show a different page based on the newly submitted data, the child node may actually be a sibling of the node that the marker was pointing to during the upward walk. Continuing in the same vein, the path back down the tree will lead to a different set or the same set of continuations being encountered, depending on the data submitted by the user.

You Make the Rules!

While it is possible to implement continuations as a simpler alternative to MVC frameworks, this style of programming does offer some distinct advantages, particularly when it comes to controlling application behavior. For example, frameworks supporting continuations typically allow for invalidating a particular one. Invalidating a continuation makes it impossible to go back to the page corresponding to that continuation (by clicking on the browser’s Back button, for instance) and change the associated form data before re-submitting the form. (Internally, the server deletes the continuation object corresponding to the continuation id. As a result, there is no continuation available to resume, and an error is reported. This error can be handled — in a manner specific to the development framework being used — by redirecting the browser to an error page, for example. Ruling out this type of action in some instances offers a much greater degree of control over your application’s processing overhead. As previously discussed, it is also possible to employ scripting code in an MVC framework to disallow certain navigational patterns. Continuations just let you do so more easily.

Unlike MVC implementations, the continuations-based approach provides a workaround to the code tangle that can result from trying to handle cloning. In the continuations-based approach, the user can enter different data on the original and the cloned windows, and submit both in parallel. The continuation is then resumed in two threads (basically, the server threads that have been assigned the handling of the two requests) with two sets of submitted values. This outcome is far preferable to what commonly happens in Web applications not based in continuations: Either such a feature is disallowed or one transaction overrides the other. Disallowing this feature is not always a good option, because users sometimes use the browser cloning feature to do what-if analyses on two sets of values prior to choosing one.

It’s also worth noting that the continuations-based approach does away with the concept of user state. With continuations, a user can have multiple states at the same time, one for each cloned page open in a browser window.

The Continuations Repository

It is important to maintain a continuations repository to manage the continuations for a Web application. One way is to have a global hash table with globally unique continuation ids maintained by the server providing the continuations infrastructure. This doesn’t preclude one user copying and reusing the continuations id belonging to a different user from the browser. To prevent such occurrences, the tree of continuations can be maintained in a user’s HTTP session, as well. In either situation, replicating the continuations repository will be necessary if running in a clustered environment. As mentioned, invalidating a continuation will cause the supporting framework to remove the entire object from the repository. Otherwise, such frameworks also provide for specifying a time to live for continuations such that expired continuations are automatically removed from the repository.

There are two options for sending the continuation id to the user’s browser: It can be embedded as a hidden field in the form that is sent back, or it can be embedded in the URL to which the form will be posted. Needless to say, encapsulating continuation ids within cookies is a bad idea, because a specific cookie is common to all cloned instances of a browser window on a machine whereas a continuation is specific to a particular instance of the browser window only.

That’s enough talk for now. The best way to make the case for continuations is to let you see them in action. In the following sections, I’ll use an example application to demonstrate the simplicity of developing Web applications using continuations. To run the example application, you will need to download the Cocoon framework from Apache, because the Java platform by itself does not support continuations. See Resources to download Cocoon and learn about other Web development frameworks that support continuations.

An Example Application

I’ll use a simple application to ease you into Web application development using continuations. From a navigational perspective, the interface for this shopping application is fairly simple. Upon accessing the first page of the application, the user is asked to enter the price and item count of the desired purchase. Upon entering this information and selecting Next, the user is taken to the next screen and asked for his category code, which determines whether he will receive a discount on the purchase amount. (Note that in this oversimplified example, it is assumed that the user will provide truthful information.) On this page, the user is asked whether the purchase should be shipped or picked up. If the shipping option is selected, the interface returns a third screen on which the user must enter the type of shipping desired: standard or express with different associated costs. If the user enters the pickup option, or upon completing the shipping option, the last screen is displayed. This screen shows, among other information, the total amount due for the purchase, which is the purchase amount, minus any given category discount, plus any shipping cost.

It is a simple application, but it will provide a good basis for learning about continuations. Before I start on the coding, I’ll take a minute to introduce the Apache Cocoon framework, for those who do not already know and love it.

Web Continuations in Apache Cocoon

Apache Cocoon is a Web development framework that allows you to dynamically publish XML content using XSL transformations. Cocoon’s support for different transformations means that you can easily present content in multiple formats. Cocoon uses a processing pipeline to describe the sequence of steps followed in handling a request and generating the corresponding response. Each pipeline describes a way of getting some input, followed by a series of processing steps to be performed on the data, and, finally, a mechanism of producing the output.

The individual components plugged in to form the pipeline are declared and wired together in what is called a sitemap. You can define multiple pipelines for a Web application and specify that different ones be called to to handle various requests based on request/environment parameters.

The components provided by Cocoon can be classified into a number of types:

• Generators and Readers are the pipeline’s input components.

• Transformers and Actions are the processing components.

• Serializers are the output components.

• Matchers and Selectors handle conditional processing.

To be useful, a pipeline must clearly contain at least a generator, or reader, and a serializer. The number of processing steps in between will depend on the business logic of the application.

Cocoon and MVC

The above-described Cocoon architecture corresponds to MVC’s Model 1 architecture in that it lacks a central controller that dispatches requests from the client tier and selects views. However, Cocoon also has provision for Model 2 architectures. In this case, the sitemap must contain an entry that specifies the controller, in addition to the regular pipeline entries. Like in any other Model 2 architecture, the controller directs the business logic that interacts with the application model. In this case, the pipeline concept is still used for handling the view, but is driven from the controller.

The first controller engine supported by Cocoon was based on a version of Rhino JavaScript from Mozilla, because this provided support for continuations as first-class objects. As you’ll see in the following example, using Cocoon with the controller means you must write the entire application as a single JavaScript program and register it as the flow controller with the sitemap specified for your Cocoon application.

All of this is much easier to understand in code than in concept. The first thing I need to do is set up the sitemap for the shopping application. I’ll then go on to take a look at how the application logic is implemented in JavaScript. Lastly, I’ll take a look at the XML files underlying some of the pages of the application with a view to demonstrating a few important concepts.

About the development environment

The development environment for the example application is Cocoon V2.1.5.1 running on Apache Tomcat Server (V5.0.18) on a Windows XP workstation. I assume that you already have downloaded the Cocoon source code, built it, and deployed the Cocoon Web application generated into Tomcat. Therefore, the article source code is for the example application alone. See Resources if you need more download or setup information for Apache Cocoon.

The Application Sitemap

My next step is to write the application logic into my FlowControl file — in this case, pos.js. As I mentioned, the file contains a function called sellItem that kicks off the application flow, as shown in Listing 4:

Listing 4. Application flow implemented in JavaScript

function sellItem()

{
var rate, qty, zone, amount, discount, total, discrate, savings, delOpt, delCost, Webcon;

var url = "page/getRateAmt";
cocoon.sendPageAndWait(url);
rate = parseFloat(cocoon.request.getParameter("rate"));
qty = parseInt(cocoon.request.getParameter("qty"));
amount = rate*qty;

url="page/getZone";
Webcon = cocoon.sendPageAndWait(url, {"rate":rate, "qty":qty});
zone = cocoon.request.getParameter("zone");

discount=0.02;

if (zone=="A")
{
if (qty >= 100)
{
discount=0.1;
}
}
else if (zone=="B")
{
if (qty >= 200)
{
discount=0.2;
}
}

discrate = 100*discount;
savings = discount*amount;

delCost=0.0;
delOpt = cocoon.request.getParameter("delOpt");
if (delOpt=="S")
{
url="page/getShipOpt";
cocoon.sendPageAndWait(url);
delCost = parseInt(cocoon.request.getParameter("delCost"));
}

total = amount + delCost - savings;
url="page/displayResult";
cocoon.sendPageAndWait(url, {"discrate":discrate, "total":total, "savings":savings,
"delCost":delCost, "amount":amount, "discount":discount, "zone":zone});
}

The cocoon object and its functions

You will notice in this example I have used an object called cocoon without having first declared it elsewhere. I didn’t need to declare cocoon because it is part of a set of default system objects provided by Cocoon for use in Flowscripts. This set of objects is called the Flow Object Model (FOM).

The cocoon object is probably the most important and used object in the FOM set and is also the point of entry into the FOM. It is a global variable that represents the current sitemap. It provides two important functions called sendPage and sendPageAndWait. Both functions pass control to the Cocoon sitemap for generating the output page.

The former function takes two arguments, one the sitemap URI of the page to be sent back to the client and the other a context object containing data that can be extracted and used to replace placeholders within the generated page.

The latter function, which I’ve used in my application logic in Listing 4, takes the same two arguments as the former, but with a difference. After the page has been generated and sent back to the client, the sendPageAndWait function generates and returns a new continuation object (also part of the FOM). At this point, the Cocoon infrastructure also internally generates a unique continuation id and stores the mapping between the two in a global structure.

The sendPageAndWait function can also be passed a function that will be automatically executed after the pipeline processing is complete, but before the continuation is generated. This is an important feature in cases where the pipeline processing requires expensive or contentious resources that should not be bundled with the rest of the execution context (because the user may ask for this continuation to be resumed after a certain amount of think time, and it wouldn’t make sense to hold on to these resources the entire time). However, we haven’t used that version of this function in our code.

The Application Logic

My next step is to write the application logic into my FlowControl file — in this case, pos.js. As I mentioned, the file contains a function called sellItem that kicks off the application flow, as shown in Listing 4:

Listing 4. Application flow implemented in JavaScript

function sellItem()

{
var rate, qty, zone, amount, discount, total, discrate, savings, delOpt, delCost, Webcon;

var url = "page/getRateAmt";
cocoon.sendPageAndWait(url);
rate = parseFloat(cocoon.request.getParameter("rate"));
qty = parseInt(cocoon.request.getParameter("qty"));
amount = rate*qty;

url="page/getZone";
Webcon = cocoon.sendPageAndWait(url, {"rate":rate, "qty":qty});
zone = cocoon.request.getParameter("zone");

discount=0.02;

if (zone=="A")
{
if (qty >= 100)
{
discount=0.1;
}
}
else if (zone=="B")
{
if (qty >= 200)
{
discount=0.2;
}
}

discrate = 100*discount;
savings = discount*amount;

delCost=0.0;
delOpt = cocoon.request.getParameter("delOpt");
if (delOpt=="S")
{
url="page/getShipOpt";
cocoon.sendPageAndWait(url);
delCost = parseInt(cocoon.request.getParameter("delCost"));
}

total = amount + delCost - savings;
url="page/displayResult";
cocoon.sendPageAndWait(url, {"discrate":discrate, "total":total, "savings":savings,
"delCost":delCost, "amount":amount, "discount":discount, "zone":zone});
}

The cocoon object and its functions

You will notice in this example I have used an object called cocoon without having first declared it elsewhere. I didn’t need to declare cocoon because it is part of a set of default system objects provided by Cocoon for use in Flowscripts. This set of objects is called the Flow Object Model (FOM).

The cocoon object is probably the most important and used object in the FOM set and is also the point of entry into the FOM. It is a global variable that represents the current sitemap. It provides two important functions called sendPage and sendPageAndWait. Both functions pass control to the Cocoon sitemap for generating the output page.

The former function takes two arguments, one the sitemap URI of the page to be sent back to the client and the other a context object containing data that can be extracted and used to replace placeholders within the generated page.

The latter function, which I’ve used in my application logic in Listing 4, takes the same two arguments as the former, but with a difference. After the page has been generated and sent back to the client, the sendPageAndWait function generates and returns a new continuation object (also part of the FOM). At this point, the Cocoon infrastructure also internally generates a unique continuation id and stores the mapping between the two in a global structure.

The sendPageAndWait function can also be passed a function that will be automatically executed after the pipeline processing is complete, but before the continuation is generated. This is an important feature in cases where the pipeline processing requires expensive or contentious resources that should not be bundled with the rest of the execution context (because the user may ask for this continuation to be resumed after a certain amount of think time, and it wouldn’t make sense to hold on to these resources the entire time). However, we haven’t used that version of this function in our code.

Understanding the Application Logic

With the above explanation, understanding the application logic should be easy. The script first asks for a page identified by page/getRateAmt to be sent to the user. This matches the first pipeline in the sitemap, causing the JXTemplateGenerator component to pick up the file screens/getRateAmt.xml and pass it on to the next component in the pipeline. Listing 5 shows the XML file for that component, getRateAmt.xml:

Listing 5. The XML file for getRateAmt

<?xml version="1.0"?>

<page>
<title>Get Rate and Quantity of item to be purchased</title>
<content>
<form method="post" action="continue.#{$cocoon/continuation/id}">
<para>Enter Rate: <input type="text" name="rate"/></para>
<para>Enter Quantity: <input type="text" name="qty"/></para>
<input type="submit" name="submit" value="Next"/>
</form>
</content>
</page>

The important point to note in this file is that the action field of the form uses a JXPath expression, #{$cocoon/continuation/id}. The expression will be automatically replaced by the continuation id generated by Cocoon when the sendPageAndWait call corresponding to its page is executed in the control script. This will cause the form to be submitted to a URL that will match the regular expression continue.*, and thereby lead to the appropriate (the second one declared in our sitemap, actually) pipeline in the sitemap being activated. (This pipeline, as you saw previously, consists only of a call statement, which will resume the continuation identified by the given id.)

Resuming the Continuation

Resuming the continuation essentially brings control back to the script on the line immediately following the call to sendPageAndWait. The next two lines extract the rate and qty parameters from the request. The next continuation is generated when the sendPageAndWait call is made for the page/getZone page, which maps to the physical file screens/getZone.xml. This call also takes a map consisting of name/value pairs, which here are basically the two parameters submitted by the user in the previous page.

To better understand how these name/value pairs work, take a look at the XML file underlying the /getZone page, shown in Listing 6:

Listing 6. The XML file underlying the getZone page

<?xml version="1.0"?>

<page>
<title>Get Buyer Category and Delivery Option</title>
<content>
<form method="post" action="continue.#{$cocoon/continuation/id}">
<para>You are buying #{qty} items, at #{rate} apiece</para>
<para>Please specify your category:<br/>
<input type="radio" name="zone" value="A">A</input><br/>
<input type="radio" name="zone" value="B">B</input><br/>
<input type="radio" name="zone" value="C">C</input><br/>
</para>
<para>Will you be picking up the item yourself from our warehouse,
or would you like it shipped?<br/>
<input type="radio" name="delOpt" value="P">Pickup</input><br/>
<input type="radio" name="delOpt" value="S">Shipping</input><br/>
</para>
<input type="submit" name="submit" value="Next"/>
</form>
</content>
</page>

This page is geared to display to the user the values he entered for the rate and quantity in the previous page. You will notice that the placeholders in this XML file are #{qty} and #{rate} — these names will be looked up in the map and automatically replaced by the corresponding values from the map when the HTML page is being assembled. As before, the action field of this form points to the continuation id for the given page, which represents the child continuation of the continuation generated for the getRateAmt page.

The rest of the application flow in Listing 4 can be understood in a similar fashion. One interesting thing to note is that the page getShipOpt will be shown to the user only if he selected “S” for getting the purchased items shipped. If the user has selected that option, a new continuation will be generated. If the user has selected “P” for pickup, then the getShipOpt page doesn’t have to be shown, and, hence, no continuation will be generated.

JavaScript vs. the Java language

This brings me to the end of the continuations-based sample application developed in JavaScript. It is also possible to develop the same application with an alternative Cocoon flow interpreter that works with pure Java language, allowing you to write the entire application logic as a single Java program. I’ll show you how the Java interpreter works in a moment, but first I’d like to consider the arguments for and against writing the program in Java language.

The most common argument against using JavaScript in place of the Java language is that the latter is a more widely known and used language with extensive IDE support, an extensive catalog of design patterns and so on. In support of JavaScript, it is dynamically typed and makes rapid prototyping (in the form of quicker write/update-deploy-test cycles) possible. As a language, it is already known to a large number of Java developers from its usage on the client browser side and is very easy to pick up in any case. JavaScript is an object-oriented language, and the Rhino implementation has very good integration with the Java platform. It is possible to access and reuse any Java class or object that exists in the application. Therefore, even with the core flow implemented in JavaScript, it is possible to implement the actual business logic in the Java language (with the classes being accessed from within the JavaScript flow at the appropriate places).

In short, no one option is very obviously better than the other, and it is entirely to your personal preference to decide which of the two languages you should be using to develop your continuations-based applications. And fortunately, Cocoon lets you choose either option.

Continuations in Java code

Newer releases of Cocoon provide support for a pure-Java interpreter of the flow script. Listing 7 shows the source code for the pure-Java interpreter:

Listing 7. The application flow implemented in Java code

import org.apache.cocoon.components.flow.java.AbstractContinuable;

import org.apache.cocoon.components.flow.java.VarMap;

public class PosFlow extends AbstractContinuable
{
public void doSellItem()
{
double rate, amount, total, savings;
double discount, discrate;
int qty, delCost;
String zone, delOpt;

String url = "page/getRateAmt";
sendPageAndWait(url);
rate = Float.parseFloat(getRequest().getParameter("rate"));
qty = Integer.parseInt(getRequest().getParameter("qty"));
amount = rate*qty;

url="page/getZone";
sendPageAndWait(url, new VarMap().add("rate",rate).add("qty",qty));
zone = getRequest().getParameter("zone");

discount=0.02;

if (zone.equals("A"))
{
if (qty >= 100)
{
discount=0.1;
}
}
else if (zone.equals("B"))
{
if (qty >= 200)
{
discount=0.2;
}
}

discrate = 100*discount;
savings = discount*amount;

delCost=0;
delOpt = getRequest().getParameter("delOpt");
if (delOpt.equals("S"))
{
url="page/getShipOpt";
sendPageAndWait(url);
delCost = Integer.parseInt(getRequest().getParameter("delCost"));
}

total = amount + delCost - savings;
url="page/displayResult";
sendPageAndWait(url, new VarMap()
.add("discrate",discrate)
.add("total",total)
.add("savings",savings)
.add("delCost",delCost)
.add("amount", amount)
.add("discount", discount)
.add("zone", zone));
}
}

As Listing 7 shows, Cocoon provides an abstract class called AbstractContinuable with implementations for the sendPage and sendPageAndWait functions. The PosFlow class extends this abstract class and contains the business logic in a method called doSellItem.The implementation of this method is exactly the same as the JavaScript implementation of sellItem in Listing 4.

Sitemap for the Java implementation

You can see the sitemap for the Java-based application in Listing 8. As you’ll note, even this looks very similar to the earlier sitemap. The only difference is that the flow language is specified as Java and the source of the script is specified as the PosFlow class.

Listing 8. The Cocoon sitemap for the Java-based implementation

<?xml version="1.0"?>

<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">

<map:flow language="java">
<map:script src="PosFlow"/>
</map:flow>

<map:pipelines>

<map:pipeline>
<map:match pattern="page/*">
<map:generate type="jx" src="screens/{1}.xml"/>
<map:transform src="context://samples/common/style/xsl/html/simple-page2html.xsl">
<map:parameter name="servletPath" value="{request:servletPath}"/>
<map:parameter name="sitemapURI" value="{request:sitemapURI}"/>
<map:parameter name="contextPath" value="{request:contextPath}"/>
<map:parameter name="file" value="/samples/flow/jxrate/screens/{1}.xml"/>
<map:parameter name="remove" value="{0}"/>
</map:transform>
<map:serialize/>
</map:match>
</map:pipeline>

<map:pipeline>

<map:match pattern="continue.*">
<map:call continuation="{1}"/>
</map:match>

<map:match pattern="">
<map:call function="sellItem"/>
</map:match>

</map:pipeline>

</map:pipelines>

</map:sitemap>

Another small difference between the JavaScript and Java implementations is in the way the id for the current continuation is accessed within the XML templates corresponding to the application’s HTML pages. You can see this yourself by studying the XML template for the getRateAmt page in the Java-based implementation, shown in Listing 9. The continuation id can be accessed by the JXPath expression #{$continuation/id}.

Listing 9. The XML file for the getRateAmt page

<?xml version="1.0"?>

<page>
<title>Get Rate and Quantity of item to be purchased</title>
<content>
<form method="post" action="continue.#{$continuation/id}">
<para>Enter Rate: <input type="text" name="rate"/></para>
<para>Enter Quantity: <input type="text" name="qty"/></para>
<input type="submit" name="submit" value="Next"/>
</form>
</content>
</page>

Pros and Cons of Continuations

As I’ve shown in the preceding sections, continuations essentially provide a way to add conversational state to Web applications. The advantages of using continuations are that unusual navigation patterns can be handled easily; it is very simple to use debugging tools to run through the entire application in one shot, instead of having to place breakpoints at multiple places in a scattered code base; and it becomes very easy to understand and communicate the program structure, as well as the possible Web navigation paths in the entire application.

The biggest problem with using continuations for Web development is that not many of the languages, frameworks, and environments commonly used to develop Web applications support them. The concept of continuations and CPS itself is seen to be arcane and not intuitive. A second big hurdle is that of how and where to store continuations. We can store them on the client side, but due to the previously mentioned issue with cookies being shared across all instances of a cloned browser window, the viable option is to store the entire continuation in a serialized form in a hidden form field. This necessitates ensuring the integrity of the continuation. We can also store them on the server side, which is what I did in the sample application, but then we have to worry about issues like garbage collection and replication across cluster nodes. Lastly, there is a lack of clarity around the efficiency (performance-wise) of continuations-based Web applications.

Conclusion

Designing and developing complex, interactive Web-based applications is daunting in itself and made all the more harder by the multitude of whimsical navigation paths that a browser allows application users to follow. Continuations provide an elegant mechanism for developing such Web applications as a single linear program that is easy to understand and debug. In this article, I have provided a basic introduction to the theory behind continuations, as well as a practical demonstration of how the continuations support in Apache Cocoon can be leveraged to develop complex Web applications. See Resources to learn more about continuations.

Resources

• Click the Code button at the top or bottom of this page to download the source code for the article.

• Download Apache Cocoon from the Cocoon homepage, the original source of Cocoon related information.

Struts Flow is a port of Apache Cocoon’s Control Flow to Struts that allows development of continuations-based applications using Apache Struts.

• Malcolm Davis’s ” Struts, an open source MVC implementation” (developerWorks, February 2001) is a good overview of MVC and the Struts framework.

• Matthias Felleisen and Amr Sabry provide an introduction to continuations in their paper ” Continuations in Programming Practice: Introduction and Survey.”

• The paper ” Applications of Continuations” (PDF) by Daniel Friedman is an often-quoted paper on the subject of continuations.

• Don’t miss this extensive collection of papers on Continuations and Continuation Passing Style.

• The paper ” Advanced Control Flows for Flexible Graphical User Interfaces” by Paul Graunke and Shriram Krishnamurthy describes a programming pattern that can be used to provide GUI programs with browser-like capabilities of cloning windows and bookmarking application pages (access the list of all related files).

• Paul Graunke, Robert Bruce Findler, Shriram Krishnamurthy and Matthias Felleisen present in their paper ” Automatically Restructuring Programs for the Web” a technique for converting interactive Web applications into scheme-based CGI programs that utilize continuations for application flow (access the list of all related files).

• Christian Queinnec describes how a browser’s navigation actions correspond to the use of continuations in a program in the following paper in PDF format: ” Inverting back the inversion of control or Continuations versus page-centric programming.”

• Download Rife, a Web development framework with support for continuations.

Seaside is a framework for developing sophisticated Web applications in Smalltalk.

• Check out the developerWorks Web architecture zone for more articles on Web application design and development.

• You’ll find articles about every aspect of Java programming in the developerWorks Java technology zone.

• Visit the Developer Bookstore for a comprehensive Listing of technical books, including hundreds of Java-related titles.

2010-05-26T11:30:02+00:00 April 22nd, 2005|Java|0 Comments

About the Author:

Abhijit Belapurkar has a bachelor's degree in computer science from the Indian Institute of Technology (IIT), Delhi, India. He has been working in the areas of architectures and information security for distributed applications for almost 10 years, and has used the Java platform to build n-tier applications for more than five years. He is a senior technical architect in the J2EE space with Infosys Technologies Limited in Bangalore, India.

Leave A Comment