Waffle provides an easy way to invoke an Controller's ActionMethod. In short the value of the method request parameter is used as the name of the ActionMethod to fire on the Controller. ActionMethod's can be thought of as a way to execute Remote Procedure Calls (RPC) onto your Controller. The ShoppingCartController code below has two ActionMethods (addToCart(long, int) and removeFromCart(long, int)):
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 want to call the addToCart(long, int) ActionMethod in the ShoppingCartController class above. We could pass the values directly or let Waffle try and resolve them by name. Take a look at html snippet below, it demonstrates 3 simple ways to pragmatically invoke the same ActionMethod:
Notice that the second argument {quantity} is wrapped between curly brackets which signals that Waffle will need to resolve its actual value. Waffle will search each each of the following (in order) until the value is found, otherwise null will be returned:
The first value 54 is not wrapped in curly brackets so Waffle will use that value directly with ActionMethod. Waffle attempts to automatically convert String values when applicable, so the first argument passed to the method will be converted to a primitive Long.
The ActionMethod annotation allows you to define the parameter names that should be used to resolve your methods arguments (order is important). In the example below Waffle will resolve the arguments for the removeFromCart(Long, int) method by searching the context hierarchy for both foo and bar to resolve the methods 2 arguments.
Notice the parameters argument, this String Array will be used to resolve the ActionMethods arguments. The arguments foo and bar will be resolved by Waffle for their actual values. Waffle will search each of the following (in order) until the value is found, otherwise null will be returned:
If you would prefer to not use annotations to resolve your ActionMethod argument values you can leverage the ParaNamer support Waffle has built in. ParaNamer allows you to have access to the parameter names from your Java classes. In other words if you have the method void addToCart(Long itemId, int quantity) ParaNamer will provide you with the values "itemId" and "quantity" as that methods parameter names. This allows you to ditch the annotations and use your Java codes parameter names directly. Waffle will use these parameter names to resolve that ActionMethod's argument values. In the example below Waffle will use "itemId" and "quantity" to resolve the arguments for the void removeFromCart(Long, int) ActionMethod.
The ParaNamer support provided by Waffle is NOT activated by default (ActionMethod annotation is the default) so you'll need to activate it in your application's web.xml. By simply adding the following snippet to your web.xml is all you need to do.
Many time when a user first visits a page you may want to ensure that the action instance is properly initialized. In other words you may want a default ActionMethod to be triggered on that Controller when no other ActionMethod has been triggered. For that Waffle provides the asDefault attribute in ActionMethod annotation. In the example below we have a CheckoutController that will calculate the shipping cost by default when no other ActionMethod has been triggered. The method that has been annotated with @ActionMethod(asDefault=true, parameters = "theCart", "customerAddress"). These two values, theCart and customerAddress, will be used to resolve that methods arguments.
Waffle will react differently depending on what is returned, or thrown, from an ActionMethod after it has been invoked. Below we define define how each case is handled in the Waffle framework:
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:
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.
Waffle also allows you to implement custom MethodInterceptors that can "intercept" calls to ActionMethods. This allows you to do pre and post processing on ActionMethods (allowing for simple AOP type of functionality). MethodInterceptor can also be chained together cleanly and easily. See the Method Interceptors for further details.