Creating a Struts 2 Action in Groovy

(N.B.: For the latest on Struts 2 and Groovy see: Groovy Works) Actions in Struts 2 are POJO's. Actions also serve the same purpose of the ActionForm from Struts 1, storing all of the data submitted by your user. Additionally Struts 2 action classes store the information you wish to display back to your user. (NB: Struts 2 also supports a ModelDriven style, where the Action does not need to store this data, but the ideas are largely the same.) Over the course of developing a Struts 2 application you will write a lot of accessors, setThis(String in), getThat(). Personally I'm sick of writing those methods. Yes, I know Eclipse could write them for me, but I don't like Eclipse, and I haven't taken the time to learn another IDE. I like my Unix shell, maven, screen and vi (vim), thank you very much. So, what's a guy to to? Groovy Beans! Groovy Beans are a way of writing classes in Groovy that comply with the Java Beans spec. Nice this is they are a lot shorter. An example:

class Person {
    String firstName, lastName, address1, address2, city, state, zip, country;
}

That creates a bean with a default no arg constructor. All of the String members that I have declared, firstName, etc., have a private scope, and Groovy create a setXxx() and a getXxx() for each member. For more information see Groovy Beans. What does this get us in Struts 2? Simple actions. Here's an example of an action that accepts an id as a request parameter, uses a service bean to look up an order with that id, and stores it for retrieval in the presentation layer (JPS's, FreeMarker, etc.).

package example;

import com.opensymphony.xwork2.ActionSupport;

public class MyAction extends ActionSupport {

    // Service Bean
    def myServiceBean;

    // Model Beans
    String id; // Id of an order in the database to go look up.
    def order; // The order we're going to display.

    String execute () throws Exception {
        order = myServiceBean.findOrder (id);
        return SUCCESS;
    }
}

Simple isn't it. No setters and getters cluttering the code. Groovy creates all of those for you. Also the service bean can be injected using Spring. (Just be sure it has a findOrder method, or you will get an Exception. You need to test for that with your integration tests.) One trick is to make sure that you declare the type of the parameters you want to take in from the http request. That will allow Strut 2 to do its type conversion for you. By declaring the id property of type String it causes Struts 2 to populate that property from the http request with a String. If you wanted it to be an int you could also declare it as an int. Simple isn't it. It also looks a lot like Java too. So, why would you want to do this? For me the motivation was search forms that did not map to a domain model object. Frequently these had a lot of fields on them. Searching for a purchase order you might want to be able to search by customer, person who entered it, date entered from, date entered to, date of shipping from, date of shipping to, product, and the list could go on. In Java you would need a setter and getter for each of these form properties. (I've tried using Map backed forms in Struts 2, but you loose the type conversion.) In Groovy this Action could be very small.

package net.vitarara.quadran.core.web.order.purchasing;

import com.opensymphony.xwork2.Preparable;
import net.vitarara.quadran.core.business.api.purchasing.PurchasingManager;
import net.vitarara.quadran.core.web.QActionSupport;
import net.vitarara.utils.VRContext;
import org.apache.commons.lang.StringUtils;

class ListPurchaseOrdersAction extends QActionSupport implements Preparable {

    // Service Beans
    PurchasingManager purchasingManager;

    // Model Bean(s)
    List carriers, issuers, destinationAreas, products, purchaseOrders, vendors;

    // Form Properties
    Date dateEnteredFrom, dateEnteredTo, dateOfReceiptFrom, dateOfReceiptTo;
    String status, issuedById, orderNumber, vendorId;
    String destinationAreaId, carrierId, productId, fob;
    Boolean dutyPrepaid;

    void prepare () throws Exception {
        // Get the data for the drop downs.
        carriers =  purchasingManager.getCarriers ();
        destinationAreas = purchasingManager.getDestinationAreas ();
        products = purchasingManager.getProducts ();
        vendors = purchasingManager.getVendors ();
        issuers = purchasingManager.getIssuers ();
    }

    /** 
     * Marshall that data from our form input to the service layer to 
     * find purchase orders.
     */
    String execute () throws Exception {
        if (isFormFilledIn () ) {
            VRContext context = new VRContext ();
            context.dateEnteredFrom = dateEnteredFrom;
            context.dateEnteredTo = dateEnteredTo;
            context.dateOfReceiptFrom = dateOfReceiptFrom;
            context.dateOfReceiptTo = dateOfReceiptTo;
            context.status = status;
            context.issuedById = issuedById;
            context.orderNumber = orderNumber;
            context.vendorId = vendorId;
            context.destinationAreaId = destinationAreaId;
            context.carrierId = carrierId;
            context.productId = productId;
            context.fob = fob;
            context.dutyPrepaid = dutyPrepaid;
            context = purchasingManager.listPurchaseOrders (context);
            this.purchaseOrders = context.purchaseOrders;
        }
        return SUCCESS;
    }
    /** Determine if the user has filled out web the form. */
    boolean isFormFilledIn () {
        if (status || issuedById || orderNumber || vendorId || 
            destinationAreaId || carrierId ||
            productId || fob || dateEnteredFrom || dateEnteredTo || 
            dateOfReceiptFrom || dateOfReceiptTo ) {
            
            return true;
        } else {
            return false;
        }   
    }   

}  

This example is definitely more complicated. First and foremost for me, the vi loving guy, I didn't have to write 20 sets of setter and getter methods. That's about 80 lines of saved typing. All of the code in the class actually does something or declares the existence of a property of the class. The boilerplate code is gone. This action still uses the type conversion that Struts 2 provides. When the user fills in the dateEnteredFrom field on the HTML form, Struts 2 will convert that to a Date object for me, because dateEnteredFrom is of type java.util.Date. Also notice I didn't need to import java.util.blah blah blah... How many time have I wished javac just imported it. No need to worry, Groovy does. Walking through the example. The class starts with property declaration. The purchasingManger is injected using Spring in my application. The rest of the properties are either used for display purposes in the form, or are used to collect user input. The prepare() method is called as normal. Notice my Groovy class implements the Preparable interface, and extends QActionSupport, both of which are Java classes. In my execute I marshal the users input from my Action into a VRContext, which implements Map. Notice I use very OGNL like notation to put the values into the map, map.somekey = value. In the isFormFilledIn() method I check to see if the user has filled in the form. Notice that if structure, if (somestring), no need to check for null, no need for something like StringUtils.isBlank, etc. Short sweet, and everything means something. If you're using the Struts 2 maven archetype, or Maven in general you can use it to compile your Groovy files to .class files. Groovy Maven Wiki Entry I'm just getting started with Groovy. If you're a Groovy master, and have pointers, please post them.