Tutorial: Part Two

This tutorial expands on the example we covered in Tutorial: Part one. If Waffle only provided what was covered in that tutorial it would not be very interesting. So let's add onto the Automobile controller class and give it the ability to both accelerate and stop. We need to add a speed field so we can maintain the current speed of the automobile. We add a getter for speed so we can make sure that the speed is in fact increasing. And we add two action methods: void accelerate(String) and void stop(). Remember we added the "automobile" to the Registrar as a Session-Level component. So each user will have their own instance of the autombile controller and, more importantly, the controller will live for the life of the users session.

Next we will have to update the view to display the value of speed and also expose the two ActionMethod so they can be triggered by the users.

For this example we will concentrate on the more interesting of the two actions, accelerate. The accelerate action is defined as automobile.waffle?method=accelerate|15. Let's dissect this to get a better understanding of what is going on. Starting from left to right we will break this request out and explain each piece separately:

automobile the Autombile.class controller was registered under the name "automobile" in the Registrar, so this name maps to that Controller
.waffle Waffle's front controller (WaffleServlet) is mapped to the url-pattern "*.waffle", see web.xml
method when Waffle handles requests it looks for the request parameter named method in order to determine if an ActionMethod should be fired on the Controller.
accelerate this is the name of the ActionMethod that Waffle will attempt to execute on the Controller.
| each | is used to delimit the argument list that should be used when invoking the ActionMethod. There is no limit to the number of arguments we can use.
15 this value will be passed as the argument to the ActionMethod being invoked (Waffle will automatically convert the String value to the appropriate type).

In short, when Waffle handles this request it is equivalent to manually calling automobile.accelerate("15");. When running this you'll see:

Advanced ActionMethod

Waffle's ability to fire ActionMethod on controller's as was shown earlier is a powerful feature. From a development perspective it is nice because it reduces much of the classic searching through the request parameters to determine what to do next. But Waffle provides even more capabilities for triggering actions.

ActionMethod arguments

Notice the void accelerate(int) ActionMethod that we used earlier. Waffle can automatically convert the String values received into their correct type (i.e. String => Integer). However, this does not mean that ActionMethod can only be passed simple types like Strings, Numbers and Booleans. Many times a developer wants, or needs, to have access to either the javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse or javax.servlet.http.HttpSession. Waffle provides a simple way to handle this, if your ActionMethods have any of these type as a dependency they will automatically be injected.

And it does not stop there! Waffle allows you to directly reference parameters and attributes by name, in either the HttpServletRequest (parameter or attribute), HttpSession or ServletContext. So imagine we have a Map instance registered as an attribute in the user's session under the name "dictionary". We can have that Map instance passed directly as an argument to the ActionMethod we want fired. Let's write a simple controller class as an example:

This controller has one ActionMethod: void myActionMethod(Map). Assume we have registered this controller under the name "sample". We can have Waffle fire this ActionMethod by submitting the url request sample.waffle?method=myActionMethod|{dictionary}. So just to make sure it's clear, we will dissect the value of the action parameter:

myActionMethod this is the name of the ActionMethod that Waffle will invoke on the controller.
| the delimiter between ActionMethod name and arguments
{dictionary} this is referencing the "dictionary" attribute registered to the HttpSession. It is equivalent to calling (Map)httpSession.getAttribute("dictionary");. But remember Waffle will search for dictionary as a key in 1) request parameters 2) request attribute 3) session attribute 4) servletContext attribute

ActionMethod return types

Waffle will react differently depending on what is returned, or thrown, from an ActionMethod after it has been invoked. Below we define defines how each case is handled in the Waffle framework:

  • View - An ActionMethod which returns an instance of org.codehaus.waffle.view.View or org.codehaus.waffle.view.RedirectView will forward or redirect, respectively. By default Waffle assumes that each View defines the full URL the user is to be sent to. This behavior can be altered by definning a different implementation of the org.codehaus.waffle.view.ViewResolver. Waffle was built to be pluggable this is the first example of how Waffle components can be customized and configured. Read the section on Waffle's pluggability for further details.
  • null or void - When an ActionMethod has a return type of void or when your ActionMethod returns a null Waffle will simply redirect back to the referring page.
  • Exception - When an exception is thrown Waffle will set the response status to 400 (BAD REQUEST) and then send the contents of the message directly to the requestor. This functionality is especially useful when handling Ajax type of requests.
  • Other - All other objects will have their toString() method called and the resulting value will be sent back to the requestor. This is similar to how Exceptions are handled, without the response status being set to an error code. This is useful for Ajax type of requests. Imagine these type of ActionMethod returning HTML or JSON messages directly.

Caveat: Ambiguous methods

Waffle determines which ActionMethod to invoke by examining the Controller with reflection. If your Controller contains an overloaded method (same name and same number of arguments) Waffle may not be able to determine which ActionMethod should be fired. For example assume you have the following two methods defined in an Controller:

public void save(String name, Object object);
public void save(String color, Map dictionary);

Attempting to invoke the ActionMethod method=save|hello|{dictionary} will cause an AmbiguousMethodSignatureException to be thrown because Waffle will not be able to determine which of the save methods should be invoked. However, the ActionMethod method=save|foo|bar will invoke the first save method, void save(String, Object), without incident. Why? Because the String value "bar" is not assignable (and cannot be automatically converted) to the Map class, so no ambiguity will exist.