Chrysalis ControllersControllers are the heart of the Chrysalis system. Controllers manage the business logic for system updates. Chrysalis controllers resemble "regular" Java classes, in the sense that they have methods with normal parameters for updating business data. They make no direct reference to the servlet API. public class CartController extends Controller { private Map items = new HashMap(); public void addItem(Long itemId) throws Exception { items.put(itemId, Item.load(itemId)); } public void removeItems(Long[] itemId) { for (int i=0; i<itemId.length; i++) { items.remove(itemId[i]); } } public void placeOrder() throws Exception { // Use ids in items collection to create an order } public Collection getItems() { return this.items.values(); } } Controllers should be written following normal object-orientated design principles: put methods together with the data they manipulate. Most of the time, controllers can be organized around the application's use cases. Defining Controllers and Controller MethodsA Chrysalis controller can be written using the normal rules of the Java language, with the following restrictions:
Simple and Complex types are defined as follows:
All public instance methods of a controller may be invoked
through the Chrysalis framework, excluding only the public methods
inherited from the Controller method parameters correspond to the request parameters in the Command URL used to invoke the controller: Catalog.editItem.cmd?itemId=234&name=Shirt&stock=120 public class CatalogController extends Controller { public void editItem(Long itemId, String name, int stock) { // Logic to update and save item data ... } } Because Java Reflection cannot identify method parameter names, these names must be specified in the controller package configuration: <!-- chrysalis.xml for the Controller package --> <config> <class name="CatalogController"> <method name="editItem" parameters="itemId, name, stock" /> </class> <!-- Other configuration ... --> </config> Note: Because controllers resemble normal Java classes, you might be tempted to use the controllers themselves as the business objects for your application. This is bad design. Chrysalis controllers are tied to the servlet environment (albeit subtly) and will not be reusable in other contexts. You should create a business object layer that is completely independent of the web environment to facilitate testing and reuse. JavaBean InitializersThere is one special kind of controller method referred to as initializer methods. Initializers return data values available through the controller. Initializers must follow these additional rules:
Although initializer methods resemble JavaBean getter methods, they do not completely comply with the JavaBean standard, because they may have method parameters. Initializers are used to create bean objects that are updated with request data and passed as parameters to other controller methods. public class CatalogController extends Controller { public Item getItem(Long itemId) throws Exception { return Item.load(itemId); } // Form data will be copied into the Item object. public void editItem(Item item) throws Exception { item.save(); setView("/showItem.jsp", "itemId", item.getItemId()); } } In the example above:
Initializers are invoked based on the parameter name, not the JavaBean's class name. These makes it possible to define more than one initializer for the same class. public class CatalogController extends Controller { public Item getItem(Long itemId) throws Exception { return Item.load(itemId); } public void editItem(Item item) throws Exception { item.save(); setView("/showItem.jsp", "itemId", item.getItemId()); } public Item getNewItem() throws Exception { return new Item(); } public void createItem(Item newItem) throws Exception { item.save(); setView("/showItem.jsp", "itemId", item.getItemId()); } } <!-- chrysalis.xml for the Controller package --> <config> <class name="CatalogController"> <method name="getItem" parameters="itemId" /> <method name="editItem" parameters="item" /> <method name="getNewItem" /> <!-- No parameters --> <method name="createItem" parameter="newItem" /> </class> <!-- Other configuration ... --> </config> Initializers are also invoked by the
<jutil:use var="order" controller="Cart" /> Initializers can create new beans, load bean data from a data source or return controller instance variable values: public class CartController extends Controller { private Order currentOrder; public Order getOrder() { return this.currentOrder; } public void placeOrder() throws Exception { Order order = new Order(); // Update order with items in the cart order.save(); this.currentOrder = order; } } In terms of OO design, normal controller methods are the mutators of the Controller class, while initializers are its accessors. Clean OO design dictates that accessors should have no side effects (do not alter system data) and mutators return no data except error messages (exceptions). Controller methods that are not initializers normally have a
return value of Invoking Controller MethodsController methods are invoked in one of two ways: through a
The <jutil:use var="order" controller="Cart" /> public class CartController extends Controller { public Order getOrder() { return this.currentOrder; } // Other code ... } To invoke a Controller Method via a command URL, the URL must include four things:
Catalog.editItem.cmd?itemId=234&name=Shirt&stock=120 The above command URL would invoke the public class CatalogController extends Controller { public void editItem(Long itemId, String name, int stock) { // Logic to update and save item data ... } } The controller method itself may have any logic necessary to the update the system. Multi-Value Controller Method ParametersControllers can process multi-value request parameters using arrays of simple types. Consider the following form with multiple checkboxes for removing items: <h1>Shopping Cart</h1> <form action="Cart.removeItems.cmd"> <table border="1"> <tr> <th>Remove</th> <th>Item</th> </tr> <tr> <td><input type="checkbox" name="itemId" value="296" /></td> <td>Hat</td> </tr> <tr> <td><input type="checkbox" name="itemId" value="689" /></td> <td>Shirt</td> </tr> <tr> <td><input type="checkbox" name="itemId" value="492" /></td> <td>Shoes</td> </tr> <tr> <td></td> <td><input type="submit" value="Remove Items" /></td> </tr> </table> </form> This form can invoke a method that takes an array of item ids as
a parameter. Each checked box will generate an additional value in
the " public class CartController extends Controller { private Map items = new HashMap(); public void addItem(Long itemId) throws Exception { items.put(itemId, Item.load(itemId)); } public void removeItems(Long[] itemId) { for (int i=0; i<itemId.length; i++) { items.remove(itemId[i]); } } } Required Parameters and DefaultsUnless otherwise specified, all controller method parameters are required. This means that if any request parameter is missing or misspelled, the Chrysalis system will throw an exception indicating the missing parameter. This is a deliberate design feature of Chrysalis; one of the most common bugs in web application development are mismatches between form field names and expected request parameters. Catalog.editItem.cmd?itemId=234&NAME=Shirt&stock=120 public class CatalogController extends Controller { public void editItem(Long itemId, String name, int stock) { // Logic to update and save item data ... } } <!-- chrysalis.xml for the Controller package --> <config> <class name="CatalogController"> <method name="editItem" parameters="itemId, name, stock" /> </class> <!-- Other configuration ... --> </config> The above example will generate a
You can define default values for Controller Method parameters. If the request parameter is missing or misspelled, the default value will be used instead: Catalog.editItem.cmd?itemId=234&NAME=Shirt&stock=120 public class CatalogController extends Controller { public void editItem(Long itemId, String name, int stock) { // Logic to update and save item data ... } } <!-- chrysalis.xml for the Controller package --> <config> <class name="CatalogController"> <method name="editItem" parameters="itemId, name, stock" defaults="DEFAULT_NONE, Unknown, 0" /> </class> <!-- Other configuration ... --> </config> In the example above:
There are three variations on the above rules for required parameters:
The default rules for Booleans interact well with HTML checkboxes, and the default rules for multi-value parameters work gracefully with "empty" lists of parameters. Required Parameters and BeansIf a controller method has a complex (JavaBean) parameter, the rules for determining required parameters change as follows:
public class CatalogController extends Controller { public Item getItem(Long itemId) throws Exception { return Item.load(itemId); } public void editItem(Item item) throws Exception { item.save(); setView("/showItem.jsp", "itemId", item.getItemId()); } } public class Item { private Long itemId; private String name; private int stock; public Long getItemId() { return this.itemId; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getStock() { return this.stock; } public void setStock(int stock) { this.stock = stock; } // Other methods: load(), save(), etc. } In the above example, the
In lists of default parameter values, bean parameters can only have two default values:
If a controller method has multiple complex parameters, combine the required parameters for all the JavaBeans and their initializers. Controllers in the Servlet SessionThe first time a controller is invoked for a particular user:
Each controller class is effectively a per-user singleton, maintaining data on behalf of that user in the user's session. This generally eliminates the need to communicate directly with the servlet session. All the usual advice for state maintenance in a sessions applies to controllers. Controller state is not automatically persistent and can be lost in the event of server failure or session timeout. Important application data should always be persisted to a permanent data store. Controller data should only be used for non-critical data and data caching to eliminate extraneous round trips to the data store. There may be circumstances where you want to flush a controller
out of the session to replace it with a fresh controller. The
" Cross-Controller CommunicationIdeally, each controller should be a self-contained unit with no references to other controllers. In practice, this may not be possible. Chrysalis provides a method to retrieve a user's controller based on its class: Controller controller = Controller.getController([class]); // To be useful, a cast operation may be needed: CartController cart = (CartController) Controller.getController(CartController.class); The View RedirectionBecause of the role controllers play in the framework, they cannot generate any output. A controller must redirect to a view after its processing is complete. It may do so in one of three ways:
The default view is specified in the
"WEB-INF/classes/ChrysalisConfig.xml" file. If no view is
specified at all, the default view will be
" <config> <controller> <package>com.domain.catalog.controllers</package> <default view="/index.jsp" errorpage="/error.jsp" /> </controller> </config> Controller methods may specify their own view in the
<!-- chrysalis.xml for the Controller package --> <config> <default view="/index.jsp" /> <class name="CartController"> <default view="/showCart.jsp" /> <method name="addItem" parameters="itemId" /> <method name="removeItems" parameters="itemId" /> <method name="placeOrder" view="/showOrder.jsp" /> </class> <!-- Other controller configuration ... --> </config> A controller can use its public class CatalogController extends Controller { // Other operations ... public void editItem(Item item) throws Exception { item.save(); if (item.isDiscounted()) { setView("/showDiscountItem.jsp"); } else { setView("/showItem.jsp"); } } } If a view needs request parameters to render correctly, a
controller can use its public class CatalogController extends Controller { // Other operations ... public void editItem(Item item) throws Exception { item.save(); setView("/showItem.jsp"); addViewParameter("itemId", item.getItemId()); } } If a view only requires a single parameter, you can set the view and add the parameter in a single method call: public class CatalogController extends Controller { // Other operations ... public void editItem(Item item) throws Exception { item.save(); setView("/showItem.jsp", "itemId", item.getItemId()); } } The controller above will target the JSP
"
Note: The current version of Chrysalis always uses
client-side redirection [ Access to the Servlet APIChrysalis controllers are designed to automate the more common interactions between application code and the servlet API:
It is impossible to anticipate all possibilities, though, and
there may be times when a developers needs access to information
not provided by the Chrysalis framework. Chrysalis provides a "back
door" to the Servlet API through static methods in the
ServletData.getRequest(); ServletData.getResponse(); ServletData.getSession(); ServletData.getApplication(); // The ServletContext You should avoid using these methods, because doing so makes it more difficult to test your application code outside the servlet engine. |