Useful Related Utilities

 < Day Day Up > 



No tool, framework, or programming language is an island. One of the arts of software development is assembling a set of complementary tools that, together, enable you to create better software faster. This section discusses several tools that are ideal for use with Struts.

Cactus

Cactus is a framework that extends JUnit. It allows you to test Web components inside the J2EE container. If you haven't already, you should adopt a process in which you test all of your code.

Some experts advocate a test-driven approach in which you write your tests before you write your code. I think that this approach helps you write better code. Testable code implies that the code has cleaner interfaces, with improved cohesion and coupling held to a minimum. Spaghetti code is difficult to test; clean code is easy to test.

If you've used JUnit before, then getting started with Cactus is easy. Cactus runs on both the client and the server. The testXXX runs on the server. Several methods are involved in the test: setUp, beginXXX, testXXX, endXXX, and tearDown (where XXX is the name of the test).

The beginXXX method is executed before testXXX and is executed on the client. This gives you an opportunity in the beginXXX method to set up request parameters. After beginXXX, Cactus "calls" ServletRedirector remotely over HTTP. ServletRedirector is a component on the server that starts tests.

Cactus sends parameter data to ServletRedirector, tells the engine which test to run on the server, and passes along any request parameter you set up in the beginXXX method. ServletRedirector instantiates a new copy of test case copies container objects to test case. Then it calls the setUp method (on the server), calls testXXX (on the server), and calls tearDown (on the server).

The endXXX method runs on the client and is passed a WebResponse object that contains the output of the Web component. The endXXX method executes on the client so that you can test the response.

Let's say we have a simple servlet to test:

 public class MapperServlet extends HttpServlet{   public void doGet(HttpServletRequest request,                       HttpServletResponse response)throws IOException{      Map paramMap = SessionMapper.mapRequestToSession(request);      PrintWriter writer = response.getWriter();      Set mapEntries = paramMap.entrySet();      Map.Entry e = null;      for(Iterator iter = mapEntries.iterator(); iter.hasNext();){        Map.Entry entry = (Map.Entry)iter.next();        String entryStr = entry.getKey() + "=" + entry.getValue();        if(useAllCaps()){          entryStr = entryStr.toUpperCase();        }        writer.println(entryStr);      }     }  } 

This servlet is simple; it takes request parameters and maps them into session scope. It then prints the parameter data out to the browser (optionally capitalizing the data based on servlet config parameters). Let's say we want to write a test that does the following: It makes sure items are mapped into session on the server with testXXX and makes sure items are sent to the browser with endXXX. We also want to send some setup data in beginXXX.

You first extend ServletTestCase:

 import org.apache.commons.cactus.*; import junit.framework.*; public class MapperServletTest extends ServletTestCase{ private MapperServlet servlet; public MapperServletTest(String name) {         super(name); } 

Next you want to override the setUp method and create an instance of Servlet under Test:

 public void setUp()throws Exception{         this.config.setInitParameter("ALL_CAPS","true");         servlet = new MapperServlet();         servlet.init(config);     } 

Next you need to write the tests for doGet (notice the addition of beginXXX, endXXX, and testXXX, where XXX is the name of the test):

 public void beginDoGet(ServletTestRequest request){      request.addParameter("foo","manchu");  }  public void testDoGet() throws Exception{      servlet.doGet(request, response);      /*maps the parameters into the session as a side effect*/      String value = (String)session.getAttribute("foo");      assertEquals("request param mapped into session", "manchu", value);  }  public void endDoGet(WebResponse response) throws Exception{      String responseString = response.getText();      boolean paramInResponse =                   responseString.indexOf("FOO=MANCHU") > -1;      assertTrue("param not found in response", paramInResponse);    } 

If your are not familiar with HttpUnit, you may want to become familiar with it. You can use it in combination with Cactus by using it with a specialized endXXX method, as follows:

 public void endDoGet(com.meterware.httpunit.WebResponse theResponse) {        WebTable table = theResponse.getTables()[0];        assertEquals("rows", 4, table.getRowCount());        assertEquals("columns", 3,                        table.getColumnCount());         } 

HttpUnit, unlike Cactus, does not extend the JUnit framework. HttpUnit provides simplified access to the HTML Document Object Model (DOM). It has a set of helper methods that make dealing with HTML responses easy.

HttpUnit can be found at http://www.httpunit.org/. Cactus is a Jakarta project and can be found at http://jakarta.apache.org/cactus/index.html.

Our example so far covers how to test a servlet. Of course, if you are developing with Struts you will not be testing many servlets; you could, however, use Cactus to test a JSP:

 public class SalesReport extends JspTestCase { ... //Setup session in setup method if needed //Setup request parameters in beginXXX if needed public void testSalesReport() {     RequestDispatcher rd =          theConfig.getServletContext().           getRequestDispatcher("/salesreport.jsp");     rd.forward(theRequest, theResponse);    //Check Session scope variable } public void endTestSalesReport(           com.meterware.httpunit.WebResponse theResponse) {      //Use HttpUnit to verify the HTML } 

If you have adopted MVC, you realize that JSPs should not really do much. It might make more sense to test the JSP through the action. This gives the action a chance to map in model items into scope so that the JSP can use them (otherwise, you would have to set up the model items in the setup method):

 public class SalesReport extends JspTestCase { ... //Setup session and request scope in setup method (need?) //Setup request parameters in beginXXX (if needed) public void testSalesReport() {     RequestDispatcher rd =          theConfig.getServletContext().           getRequestDispatcher("/generateSalesreport.do");     rd.forward(theRequest, theResponse);    //Check Session scope variable } public void endTestSalesReport(           com.meterware.httpunit.WebResponse theResponse) {      //Use HttpUnit to verify the HTML with HttpUnit } 

In addition to testing JSP and actions, you can test custom tags that you write. Suppose you have a tag that looks up an entity bean and prints out a cmp field. This is similar to bean:write except that it works with entity beans:

 public class EJBWriteTag extends WriteTag {     public int doStartTag() throws JspException {         doStartTagPrecondition();         Object bean = null;         Object primaryKey = getPrimaryKey();           ...             /* Look up the bean */         bean =               LocalFinderUtils                   .findByPrimaryKey(primaryKey, name);             /* Get the value from the bean */         String output =                  BeanUtils.getProperty(bean, property);             /* Write out the value */         ResponseUtils.write(pageContext, output); ... 

The test you write will simulate the custom tag lifecycle. You will need to populate the attributes of the custom tag. Finally, you check the output to see if the correct value appears:

 public class EJBWriteTagTest extends JspTestCase {     public final static String DEPT_NAME = "TESTZZZ";      ...     public void testEJBWriteTag ()throws Exception{         EJBWriteTag tag = new EJBWriteTag();         tag.setPageContext(pageContext);         Integer id = Util.createDept(DEPT_NAME);         request.setAttribute("id", id);         tag.setIdName("id");         tag.setName("DeptBean");         tag.setProperty("name");         tag.doStartTag();         Util.killDept(id);     }     public void endEJBWriteTag (WebResponse response){         String output = response.getText();         assertEquals(DEPT_NAME, output);     } } 

We did not cover Cactus in detail in this book. For more information on using Cactus and JUnit in your projects, check out Java Tools for Extreme Programming, also by Rick Hightower, et al.

StrutsTestCase

Like Cactus, StrutsTestCase extends the JUnit framework. StrutsTestCase is geared for testing Struts components. It provides two ways to test actions: a mock object approach and a Cactus approach. If you have adopted a test-centric development process and you are developing in Struts, you should start using StrutsTestCase. Here is an example using StrutsTestCase to test an action mapped under the path /listDepartments:

 import servletunit.struts.CactusStrutsTestCase; public class DeptListingActionTest                  extends CactusStrutsTestCase {     public void testDeptListingAction(){         setRequestPathInfo("/listDepartments");         actionPerform();         verifyForward("listing");         verifyNoActionErrors();     } } 

This code test ensures that the action is forwarded to "listing" and that no action errors are mapped into scope. You can find StrutsTestCase at http://strutstestcase.sourceforge.net/.

XDoclet

If you are a J2EE development veteran, you know that keeping code in sync with deployment descriptors and configuration files can be tedious. Often you may need to reuse components with other applications or in other environments, such as other application servers or with other database systems. You need to keep a separate deployment descriptor for each application/environment combination, even if only one or two lines of the large deployment descriptor changes. This can really slow down development. At times you may feel you spend more time syncing deployment descriptors than writing code.

XDoclet facilitates automated deployment descriptor generation. As a code-generation utility, it allows you to tack on metadata to language features such as classes, methods, and fields using what looks like JavaDoc tags. Then, it uses that extra metadata to generate related files, like deployment descriptors and source code. This concept has been coined attribute-oriented programming (not to be confused with aspect-oriented programming, the other "AOP").

XDoclet generates these related files by parsing your source files in a way similar to the method the JavaDoc engine uses to parse your source to create JavaDoc documentation. In fact, earlier versions of XDoclet relied on JavaDoc. XDoclet, like JavaDoc, not only has access to these extra metadata that you tacked on in the form of JavaDoc tags to your code, but also has access to the structure of your source-that is, packages, classes, methods, and fields. It then applies this hierarchy tree of data to templates. It uses all of this (along with templates that you define) to generate what would otherwise be monotonous manual creation of support files.

XDoclet ships with an Ant task that enables you to create web.xml files, ejb-jar.xml files, and much more. Note that XDoclet Ant tasks do not ship with the standard distribution of Ant. You will need to download the XDoclet Ant tasks from the XDoclet site at http://xdoclet.sourceforge.net/.

You can also use XDoclet to generate Struts configuration files such as validation.xml and struts-config.xml. Using XDoclet simplifies tasks like adding validation support; for example, the following XDoclet-enabled version of inputForm uses XDoclet tags to mark up the user form field:

 /**  * @author rhightower  * @struts.form    name="inputForm"  *  */ public class InputForm extends ValidatorForm {     private String userName;      public String getUserName() {         return userName;     }     /**      * @struts.validator type="required"      * @struts.validator type="mask" msgkey="inputForm.userName.mask"      * @struts.validator type="minlength" arg1value="${var:minlength}"      * @struts.validator type="maxlength" arg1value="${var:maxlength}"      * @struts.validator-var name="minlength" value="5"      * @struts.validator-var name="maxlength" value="11"      * @struts.validator-var name="mask" value="^[a-zA-Z]{1}[a-zA-Z0-9_]*$"      */     public void setUserName(String string) {         userName = string;     } ... 

The class tag @struts.form denotes that this is a Struts ActionForm. The @struts.validator tag is a method-level tag, and it is used four times to associate the corresponding userName property (form field) with the required, mask, minlength, and maxlength rules. The @validator-var tag sets up the variables needed by the rules. Our XDoclet-enabled form generates the following validation.xml file entries:

 <formset>     <form name="inputForm">             <field property="userName"                    depends="required,mask,minlength,maxlength">                 <msg                   name="mask"                   key="inputForm.userName.mask"/>                 <arg0 key="inputForm.userName"/>                 <arg1                     name="maxlength"                   key="${var:maxlength}"                     resource="false"                 />                 <arg1                     name="minlength"                   key="${var:minlength}"                     resource="false"                 />                 <var>                   <var-name>minlength</var-name>                   <var-value>5</var-value>                 </var>                 <var>                   <var-name>maxlength</var-name>                   <var-value>11</var-value>                 </var>                 <var>                   <var-name>mask</var-name>                   <var-value>^[a-zA-Z]{1}[a-zA-Z0-9_]*$</var-value>                 </var>             </field>     </form> 

You will find the Validator framework much easier to use with XDoclet support. The XDoclet templates and tasks for the Validator framework were created by famed Struts, Ant, and XDoclet expert Erik Hatcher.

You can also use XDoclet to generate Struts configuration files. You do this by marking up the action as follows:

 /**  * @author rhightower  * @struts.action path="/inputSubmit" name="inputForm"  *                validate="true" input="/input.jsp"  *                attribute="employeeForm"  *  * @struts.action-forward name="success" path="/success.jsp"  * @struts.action-forward name="resubmit" path="/resubmit.jsp"  *  * @struts.action path="/input" parameter="loadAddForm">  * @struts.action-forward name="input-success" path="/input.jsp"/>  *  */ public class InputAction extends Action {     public ActionForward execute(         ActionMapping mapping,         ActionForm form,         HttpServletRequest request,         HttpServletResponse response)         throws Exception { 

This code generates the following entries in your struts-config.xml files:

     <action-mappings>      <action        path="/inputSubmit"        type="masteringStruts.InputAction"        name="inputForm"        scope="request"        input="/input.jsp"        unknown="false"        validate="true"      >        <forward          name="success"          path="/success.jsp"          redirect="false"        />        <forward          name="resubmit"          path="/resubmit.jsp"          redirect="false"        />        <forward          name="input-success"          path=""          redirect="false"        />      </action>      <action        path="/input"        type="masteringStruts.InputAction"        unknown="false"        validate="true"      >        <forward          name="success"          path="/success.jsp"          redirect="false"        />        <forward          name="resubmit"          path="/resubmit.jsp"          redirect="false"        />        <forward          name="input-success"          path=""          redirect="false"        />      </action> 

I find that generating action mappings works best for simpler actions (actions that don't have more than one mapping associated with them). You can keep your more complex action mappings along with your global forwards, global exception handling, and more in merge files. Merge files are merged into the final Struts configuration file during XDoclet generation.

Another problem with mapping actions is that you end up hard-coding the forward and input path into the action (albeit as a comment). At first blush, this defeats the purpose of separating all of this within the Struts configuration file to begin with.

You can alleviate this problem by using Ant properties. Any value can be replaced by an Ant property. Because XDoclet runs inside Ant, it has access to Ant properties. For example, notice the value of the success forward's path:

 /**  * @author rhightower  * @struts.action path="/inputSubmit" name="inputForm"  *                validate="true" input="/input.jsp"  *                attribute="employeeForm"  *  * @struts.action-forward name="success" path="${input.success}"  * @struts.action-forward name="resubmit" path="/resubmit.jsp"  *  * @struts.action path="/input" parameter="loadAddForm">  * @struts.action-forward name="input-success" path="/input.jsp"/>  *  */ public class InputAction extends Action { 

It is now set to ${input.success}, which means whatever value ${input.success} is set to when you run the Ant build script is the value that is generated output for the path of the success forward to the Struts configuration file. This gives you the benefits of both worlds and allows you to vary the paths at build time.

Tto run XDoclet from Ant, you need to import the XDoclet task into your build script as follows:

     <path >         <fileset dir="/tools/xdoclet/lib" includes="*.jar"/>     </path>     <path >       <fileset dir="${j2ee.libs}">           <include name="**/*.jar" />       </fileset>       <fileset dir="web\WEB-INF\lib">           <include name="**/*.jar" />       </fileset>     </path> ...        <taskdef             name="webdoclet"             classname="xdoclet.modules.web.WebDocletTask">             <classpath>                 <path ref/>                 <path ref/>             </classpath>         </taskdef> 

Then to start using the XDoclet, you use the webdoclet task inside a target, like this:

         <webdoclet destdir="meta-data/web"             force="true"             mergedir="meta-data/web">             <fileset dir="src">               <include name="**/*Form.java"/>               <include name="**/*Action.java"/>             </fileset>             <strutsconfigxml validatexml="true" version="1.1"/>             <strutsvalidationxml omitdtd="true"/>         </webdoclet> 

A full discussion and coverage of XDoclet is beyond the scope of this book. As you can see, it is a powerful tool that can help simplify your J2EE and Struts development efforts. Excellent online resources for XDoclet are available. There is a multipart tutorial on XDoclet written by Rick Hightower on IBM developerWorks. You'll find a series of example applications built with XDoclet at http://xpetstore.sourceforge.net/. In addition, Erik Hatcher provides an example application (which comes with documentation on how to use XDoclet) at http://www.ehatchersolutions.com/JavaDevWithAnt/.

If you are doing Struts development, you should seriously consider using XDoclet to generate your configuration files.



 < Day Day Up > 



Professional Jakarta Struts
Professional Jakarta Struts (Programmer to Programmer)
ISBN: 0764544373
EAN: 2147483647
Year: 2003
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net