Continuations are a feature in WebWork, borrowed from the RIFE project, that allow for extremely simple state management and wizard-like functionality.
Setting it UpSetting up continuation support requires identifying the base package that your classes are in. This is done in webwork.properties using the key webwork.continuations.package. Typically, this can be the root package that your classes are found in, such as com.acme. Once you've done this, WebWork will analyze your classes and automatically apply continuation support for any class that uses the continuation features - specifically a class that extends ActionSupport that has an execute() method that calls a pause() method. URL ConcernsBecause continuations require the state of your flow be managed by WebWork, it is up to you to make sure your application inform WebWork what the flow's ID is. This is done via a continue parameter that provides a unique ID for every request in the flow. Assuming you are generating your URLs using the URL tag or the Form tag, this is handled for you automatically. If you are not using these tags, continuations will not work. Interceptor ConcernsBecause continuations radically change the way your actions are invoked, it is important to understand how this affects interceptors. The most important think to know is that continuations kick in only when the execute() method is called. This means that on every request (regardless of whether it is a new request or a continuation), the interceptors will be called. This is what makes it possible to apply new request parameters to your action even though the rest of the call stack appears to look the same. This is generally exactly what you would wante, except some interceptors, namely the Execute and Wait Interceptor and possibly the Token Session Interceptor, have very different expectations about the workflow/lifecycle of the action invocation. In these cases, continuations should not be used. ExampleGetting started with continuations is extremely simple. The biggest thing to get used to is the very different conversational style with application workflow. Typically, you might have used session variables or hidden form fields to pass the state around. Using continuations, you use the Java language to handle that state. See the following body of a Guess class extending ActionSupport: public class Guess extends ActionSupport implements Preparable { int guess; public void prepare() throws Exception { // We clear the error message state before the action. // That is because with continuations, the original (or cloned) action is being // executed, which will still have the old errors and potentially cause problems, // such as with the workflow interceptor clearErrorsAndMessages(); } public String execute() throws Exception { int answer = new Random().nextInt(100) + 1; int tries = 5; while (answer != guess && tries > 0) { pause(Action.SUCCESS); if (guess > answer) { addFieldError("guess", "Too high!"); } else if (guess < answer) { addFieldError("guess", "Too low!"); } tries--; } if (answer == guess) { addActionMessage("You got it!"); } else { addActionMessage("You ran out of tries, the answer was " + answer); } return Action.SUCCESS; } public void setGuess(int guess) { this.guess = guess; } } Note how the class keeps the state (tries, in this example) as a local variable in the execute() method. WebWork's continuations will automatically pick up the invocation after the pause() method call and will restore all local variables, as if the logical loop is continuing "magically" (read on for more info on how it works). The view is nothing special, except for that fact that it adheres to the URL concerns and uses the Form tag to render the URL. This makes sure that the continue parameter is included in all requests. <html> <head> <title></title> </head> <body> <#list actionMessages as msg> ${msg} </#list> <@ww.form action="guess" method="post"> <@ww.textfield label="Guess" name="guess"/> <@ww.submit value="Guess"/> </@ww.form> </body> </html> Advanced: How it WorksContinuations are not magic, though sometimes they might seem like they are. In fact, they work by using some very intelligent byte-code manipulation. This means that in order to use continuations, your deployment environment allow for custom class loaders to handle loading your actions. Typically this is not a problem, but it should be called out. Once the class is requested to be loaded, WebWork will hand off the request to the RIFE/Continuations module, which will then check a few conditions:
If the answer is yes to all three conditions, the class is then instrumented and the execute() method is rewritten with try/catch code, goto statements, and intelligent "state restoration" code. All this happens transparently and does not affect the ability to debug the class or otherwise code it. See the pause() method JavaDocs in the ActionSupport class for more info: Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return the specified result, such as #SUCCESS, #INPUT, etc. The next time this action is invoked (and using the same continuation ID), the method will resume immediately after where this method was called, with the entire call stack in the execute method restored. Note: this method can only be called within the #execute() method. |