AppFuse

NOTE: This wiki and its contents is for AppFuse 1.x. If you'd like to use AppFuse 2.x, please see the new wiki at http://appfuse.org. Thanks!

What is AppFuse?

AppFuse LogoCheck out our Frappr! AppFuse is an application for "kickstarting" webapp development. Download, extract and execute ant new to instantly be up and running with a Tomcat/MySQL app. Uses Ant, XDoclet, Spring, Hibernate (or iBATIS), JUnit, StrutsTestCase, Canoo's WebTest, Struts Menu, Display Tag Library, OSCache, JSTL and Struts (or Spring MVC). The Spring Framework has greatly enhanced AppFuse since February 2004. It's used throughout for its Hibernate/iBATIS support, declarative transactions, dependency binding and layer decoupling. This clean and simple framework has greatly reduced the complexity of AppFuse, and also eliminated many lines of code. In short, for J2EE - it's the best thing since sliced bread. To learn more about what AppFuse is check out this wiki page.

Features include Authentication (using Acegi Security), Remember Me, Self Registration, Password Hint and GZip Compression. The fuse to start your apps.

Live Demos
Screenshot

Video: New Project and Feature Tour
Setup Screenshot
Video: Code Generation with AppGen
AppGen Screenshot

If you want to get started with AppFuse right away, view the QuickStart Guide. If you want to learn more about AppFuse's architecture view my "About AppFuse" PowerPoint" or checkout a live demo. I also wrote a recent presentation for Denver's No Fluff Just Stuff conference.

For HowTos when developing with AppFuse, view the AppFuse Tutorials.

For common questions and howto's, view the FAQ, the mailing list archive or visit the AppFuse IRC channel. AppFuse's project homepage is at http://appfuse.dev.java.net.

To see what's next for AppFuse, see the Roadmap.

October 23, 2006 - AppFuse 1.9.4

This release's major new features are upgrading to Spring 2.0, Hibernate 3.2, and Facelets + Ajax4JSF integration for the JSF option. In addition, many libraries have been fixed and a few bugs have been squashed.

July 11, 2006 - AppFuse 1.9.3

This release is primarily a bug fix release, but also contains upgrades to several dependent libraries, including Acegi Security 1.0.1.

June 6, 2006 - AppFuse 1.9.2

This release includes CSS Framework integration, EMMA code-coverage support and AppGen sub-package support. Thanks to the CSS Framework Design Contest Winners, Doug Hays and Mika Göckel for their help with this release.

April 7, 2006 - AppFuse 1.9.1

This release includes improvements and upgrades to Tapestry 4.0.1, WebWork 2.2.2, as well as support for using AppGen to reverse engineer database tables (using Middlegen). iBATIS is now supported by AppGen and a Create DAO tutorial has been put together for iBATIS. iBATIS and Middlegen support were provided by Bobby Diaz - thanks Bobby! Also, a big thanks goes to Mika Göckel for writing an XFire Tutorial and installer. To install and configure AppFuse for development, see the QuickStart Guide. Thanks to all the sponsors who have contributed products and free hosting to the AppFuse project.

January 14, 2006 - AppFuse 1.9

This release includes full Acegi Security integration, support for Ajax with DWR and Scriptaculous and many bug fixes/upgrades. In addition, AppFuse can now be deployed without configuring your appserver. Of course, if you'd prefer to use a JNDI DataSource, that's easy as well. To install and configure AppFuse for development, see the AppFuseQuickStart. Thanks to all the sponsors who have contributed products and free hosting to the AppFuse project.

August 27, 2005 - AppFuse 1.8.2

​​​​This release is mostly a bug fix release with no new features. It also includes upgrades to Acegi Security and the Spring Framework. Thanks to all the sponsors who have contributed products and free hosting to this project.

June 15, 2005 - AppFuse 1.8.1

This release is mostly a bug fix release with no new features. It also includes many upgrades to the core libraries (Hibernate, Spring, iBATIS, MyFaces). Thanks to all the sponsors who have contributed products and free hosting to the AppFuse project.

June 15, 2005 - Thogau's Tutorials

Thomas Gaudin has put together a couple of detailed and easy-to-follow tutorials on his site.

May 6, 2005 - JIRA Installed

AppFuse Videos created and setup a new JIRA Issue Tracker. Thanks to Atlassian for the free JIRA license and to Contegix for hosting it. Also thanks to KGB Internet Solutions for sponsoring the hosting of the AppFuse Demos.

April 29, 2005 - AppFuse 1.8

This release replaces Container Managed Authentication (CMA) with Acegi Security. Other major features include numerous bug fixes to AppGen and a refactoring of build.xml to use Ant 1.6 features. Eclipse and IDEA project files were also improved so you can easily run tests from within your IDE. A MyJavaPack all-in-one installer was also added so you can download everything you need for AppFuse at once. Eclipse and its plugins were not included in the initial release, but may be in a future release.

December 8, 2004 - AppFuse 1.7

This release adds support for JSF/MyFaces and Tapestry as web framework options. AppGen has been updated to work with both of these frameworks and I added new tutorials as well. You can read about my experience in Integrating JSF and Tapestry into AppFuse.

November 9, 2004 - AppFuse 1.6.1

This release is primarily a bug fix release, but it also contains a slick "AppGen" tool for generating full CRUD (with sample data and tests) from a POJO. AppGen essentially automates everything you do in the tutorials. I still encourage users to read through and do the tutorials in order to learn the code that is being generated. Learn more about AppGen.

October 9, 2004 - AppFuse 1.6 Released

This release's main features are swapping out Tiles for SiteMesh and adding WebWork as a web framework option. I also refactored the Struts Action tests to use MockStrutsTestCase and eliminated the need to use Cactus for running web tests. This reduced test execution time by over 50%. The relationship between users and roles was re-worked to take advantage of Hibernate's features as well.

May 27, 2004 - AppFuse 1.5 Released

This release has lots of modifications that I've been meaning to make for quite some time. Specifically (1) removing the dependency on j2ee.jar and (2) removing Struts from the services layer. I also made improvements to Spring and its context file loading so you should be able to run unit tests from your IDE.

Other notables include full i18n support (with translations in Dutch, Portuguese and Chinese), improved setup-tomcat target (no additional JARs needed now), and an option to use Spring's MVC framework instead of Struts - with full tutorials! If you'd like, you can read more about my conversion from Struts to Spring. Enjoy!


2004.03.01 - AppFuse 1.4 Released

This release involves many changes: re-arranging packages/directories, Spring integration, Remember Me refactorings and I also added iBATIS as a persistence option. I also spent a lot of time going through the tutorials to make sure they are up to date. I've been using AppFuse 1.4 for a few weeks on my current project, and I really do like the way Spring makes it easy to configure Hibernate, Transactions and Interface->Implementation relationships. If you're interested in upgrading your AppFuse 1.x app to use Spring, you can checkout this howto.

I also made the leap and moved the AppFuse project from SourceForge to java.net. This is mainly so I have more control over mailing lists and adding other developers. As of today, CVS files in SourceForge and Java.net are the same - but I'll only be updating Java.net from here on out. I also have released files in both projects, but will only use java.net in the future.

Here's a specific rundown of all the changes from the changelog.


2004.01.16 - AppFuse 1.3 Released

This release fixes a few compatibility issues with Resin and other databases - specifically PostgreSQL and DB2. The major new functionality in this release is Easy Database Switching. Basically, you can very easily switch from using MySQL to PostgreSQL by only changing a few properties in your build.properties. I implemented this on my current project last week because I do most of my development (at the client) on a PowerBook. The client wants to deploy onto a DB2 database - and there is not DB2 install for the Mac. Since Hibernate allows you to easily switch between databases, I figured I could develop using MySQL on the Mac, but have the default (CVS version) use DB2. One of the things I didn't want to do was to have a build.properties.sample, because I love projects that "just work" when you type "ant". So I changed the the build process so that database.properties is generated from default settings (MySQL) or the settings in build.properties (if specified). As part of the build process, Ant looks for the following build.properties files:

What this allows you to do is to take your customized database settings and put them in ~/.build.properties and they'll be applied to any AppFuse-derived project. This makes it easy to keep the CVS version of your project tied to one database and a developer's local version tied to a different database.

While it's true that you'll most likely only talk to one database during the duration of your project, this exercise proves that it's easy to migrate from MySQL to another database. It also proves that AppFuse can easily integrate with other database (at least as of this release). Slick stuff IMO. Here's a specific rundown of all the changes from the changelog.


BTW, I hid all the older releases of AppFuse to avoid download confusion - if you need to download an older version, please let me know.

2004.01.08 - AppFuse and ant 1.6.0

Upgrading from ant 1.5.4 to 1.6.0 necessitates the following change in build.xml (just FYI):

===================================================================
RCS file: /cvsroot/struts/appfuse/build.xml,v
retrieving revision 1.37
diff -r1.37 build.xml
5,6c5,6
<     <!ENTITY properties SYSTEM "file:properties.xml">
<     <!ENTITY app-settings SYSTEM "file:app-settings.xml">
---
>     <!ENTITY properties SYSTEM "file:./properties.xml">
>     <!ENTITY app-settings SYSTEM "file:./app-settings.xml">
otherwise ant complains about file:properties.xml could not be found.
Friday, January 10th: Fixed in CVS. However, there's still issues with running Canoo's WebTest with 1.6.0

2003.12.20 - AppFuse 1.2 Released

This is primarily a bug fix release. Here are the details from the release notes:


2003.12.12 - AppFuse 1.1 Released

This biggest feature in this release is Documentation. I finally found the time to write up some Tutorials on developing with AppFuse. They're on this wiki and also in the "docs" folder of the binary and source downloads. In writing this documentation, I went through almost all aspects of the code with a fine-tooth comb made sure it's doing what I want it to do.

I was finally able to get things working with J2EE 1.4, which basically involved removing j2ee.jar from my MailUtil's classpath and just including activation.jar and mail.jar. If you're not there yet, simply change the paths for activation.jar and mail.jar in properties.xml (look for common.compile.classpath). You can use j2ee.jar instead of mail.jar and activation.jar with J2EE 1.3 and 1.4 B2.

I was also able to get all unit tests to pass on Tomcat 5, and the "setup-tomcat" target now supports Tomcat 5. I wasn't able to get "Remember Me" to work - see the tomcat-user mailing list for more details.

Included in this release are upgrades to Hibernate 2.1 Final and Display Tag 1.0 B2. For a complete changelog, view the README.txt in CVS.

2003.11.30 - AppFuse 1.0 Released

I feel this release deserves the big 1.0 designation because it is an up-to-date representation of my learnings and my perceived best practices in building web applications. Of course, as I learn more, I will continue to push out new releases.

In this release, I did a lot of refactoring and enhancements to existing features. The DAO and Manager interfaces are no longer tied to Struts or Hibernate. Hibernate's Session object is now passed as an argument into Manager and DAO constructors, rather than method signatures. The DAOFactory was refactored by Bear Giles to use reflection to instantiate Hibernate DAO's. Now, if you add a new DAO, you don't have to edit DAOFactory and DAOFactoryHibernate. To insantiate a new DAO, the code is now:

LookupDAO dao = (LookupDAO) DAOFactory.getInstance(conn, LookupDAO.class);

...where conn is a connection object retrieved from ServiceLocator or ActionFilter. When you add new POJOs, you still have to add them to ServiceLocator (for JUnit tests) and hibernate.cfg.xml, which is kindof a pain. I'd like to figure out a way to tell Hibernate to just look in appfuse-ejb.jar.

The Remember Me feature has been refactored so the username and password cookies are only available under the /appfuse/security url-pattern. I also changed the posting to "j_security_check" in LoginServlet from response.sendRedirect to an HTTP POST, using Jakarta Common's HttpClient. The reason I have a LoginServlet vs. just using action="j_security_check" in my <form> is to encrypt passwords.

I've developed 3 different applications using AppFuse (struts-resume is one of them), and I have found that it's a pain to upgrade to new versions of AppFuse. Because of this, I don't recommend upgrading unless you really need to. I will be upgrading struts-resume to AppFuse 1.0, but I doubt I'll upgrade it to any future AppFuse releases - it's just too much work for not much reward.

Older releases can be found at http://raibledesigns.com/downloads.

If you'd like to encourage me to improve AppFuse, you can make a donation.

AppFuseAntTasks

This page contains a listing of the most common Ant targets in AppFuse that I use in my daily development. For a complete list of tasks, type "ant -projecthelp" at the command line.

TaskDescription
setupSets up database, tomcat and deploys expanded war
cleanRemoves build artifacts
compileCompiles everything
deployCompiles and deploys everything to Tomcat
deploy-webDeploys JSPs and static web content to Tomcat
installInstall application in Tomcat using Tomcat's Manager app. Great for deploying to remote servers.
listList Tomcat's installed applications
refreshUndeploys, cleans, then re-deploys. Nice for when you swear it should be working, but your change doesn't show up
reloadReload application in Tomcat
removeRemove application in Tomcat
setup-dbcreates database and populates it with sample data
setup-tomcatcopies jdbc driver and context.xml to Tomcat
test-allruns all tests for dao, service and web
test-daoTest dao module
test-serviceTest service module
test-webRuns Action/Controller tests using Mocks (no container required)
test-jspRuns Canoo WebTests in tomcat (starts/stops server, Tomcat should be stopped)
test-canooRuns Canoo WebTests in Tomcat when it's already running
test-reportsGenerate test reports
undeploydelete war file and directory from $CATALINA_HOME/webapps

AppFuseCruiseControl

It's pretty easy to setup your AppFuse application to work with CruiseControl. You can either download the files from this page or if you're using 1.6.1+ - they're located in the extras/cruisecontrol directory. Thanks to Mike Clark and Jared Richardson for their help getting this work.

NOTE: I recommend setting up CruiseControl on a Linux machine if you have one available

To run CruiseControl on your AppFuse project, perform the following steps:

1. Download and install CruiseControl 2.4.1. Extract it to your $TOOLS_HOME directory, wherever that may be.

2. After you've installed CruiseControl, download the following files and put them in the cruisecontrol-2.4.1 ($CC_HOME) directory.

The latest version of these files can be found in CVS or in your project's extras/cruisecontrol directory.

3. Modify config.xml for your e-mail address and project name.

4. Modify build.xml so it points to your CVS server and project.

5. Run "ant" in the current directory or checkout your project into the "projects".

6. Run cruisecontrol.bat (Windows) or cruisecontrol.sh (Unix).

NOTE: If you're using Subversion instead of CVS, download svnant. Extract the download into your work directory, delete everything but it's "lib" directory and adjust the path in build.xml accordingly.

I was puzzled why my HTML E-Mails didn't contain all the pretty formatting like Spring's does. Then I looked at their latest archived one and noticed it looks the same on Gmane as it does in GMail. I'd recommend sending your build notifications to an e-mail account that can be read with a good HTML e-mail reader.

If you want to use CruiseControl to build your Equinox project, that's in Equinox's CVS.

Setup CruiseControl Daemon

After getting all the above working, I setup CruiseControl to run as a daemon. I has some issues with getting these instructions working, so I pinged the mailing list. After a few back-and-forth e-mails, the following solution worked for me on Suse 10.
#!/bin/sh 
#content of /opt/cruisecontrol/init script
# chkconfig: 345 99 05 
# description: CruiseControl build loop (see /home/tools)

# based on http://confluence.public.thoughtworks.org/display/CC/RunningCruiseControlFromUnixInit
# adapted for multiple projects

#
# Cruise Control startup: Startup and kill script for Cruise Control
#

PATH=/sbin:/usr/sbin:/usr/bin:/bin
export PATH

NAME=cruisecontrol
DESC="CruiseControl 2.2 continuous integration build loop"

CC_USER=mraible
CC_WORK_DIR=/opt/tools/cruisecontrol
CC_INSTALL_DIR=/opt/tools/cruisecontrol

CC_DAEMON=$CC_INSTALL_DIR/cruise.sh
CC_CONFIG_FILE=$CC_WORK_DIR/config.xml
CC_LOG_FILE=$CC_WORK_DIR/cruisecontrol.log
CC_PORT=8082
CC_RMIPORT=
CC_COMMAND="$CC_DAEMON -configfile $CC_CONFIG_FILE -port $CC_PORT -rmiport $CC_RMIPORT"

# overwrite settings from default file
if [ -f /etc/default/cruisecontrol ]; then
  . /etc/default/cruisecontrol
fi

test -f $CC_DAEMON || exit 0

if [ `id -u` -ne 0 ]; then
        echo "Not starting/stopping $DESC, you are not root."
        exit 4
fi

# PPID is read-only in my shell - GNU bash, version 2.05b.0(1)-release (i586-mandrake-linux-gnu)
PARPID=`ps -ea -o "pid ppid args" | grep -v grep | grep "${CC_DAEMON}" | sed -e 's/^  *//' -e 's/ .*//'`

if [ "${PARPID}" != "" ]
then
  PID=`ps -ea -o "pid ppid args" | grep -v grep | grep java | grep "${PARPID}" | \
      sed -e 's/^  *//' -e 's/ .*//'`
fi

case "$1" in
 
  'start')
  # going into the work dir allows for use of relative PATHs in the config file
    cd $CC_WORK_DIR
    su $CC_USER -c "$CC_COMMAND >> $CC_LOG_FILE 2>&1" & RETVAL=$? 
    echo "$NAME started with jmx on port ${CC_PORT}"
    ;;

  'stop')
    if [ "${PID}" != "" ]
    then
     kill -9 ${PID} ${PARPID}
      $0 status
      RETVAL=$?
    else
      echo "$NAME is not running"
      RETVAL=1
    fi
    ;;

  'status')
    # echo PARPIDs $PARPID
    # echo PIDs $PID
    kill -0 $PID >/dev/null 2>&1
    if [ "$?" = "0" ]
    then
      echo $NAME \(pids $PARPID $PID\) is running
      RETVAL=0
    else
      echo "$NAME is stopped"
      RETVAL=1
    fi
    ;;

  'restart')
    $0 stop && $0 start
    RETVAL=$?
    ;;

  *)
    echo "Usage: $0 { start | stop | status | restart }"
    exit 1
    ;;
esac
#echo ending $0 $$....
exit 0;

That's it - you should be able to run /etc/init.d/cruisecontrol as root and CC will check for new updates every hour.

AppFuseEclipse

AppFuse, by default, is configured to be an Eclipse project. When you run ant new to create a new project, you will get the .project and .classpath files to start your project with. However, you will need to make a few easy setting changes in Eclipse (particularly to run Ant). This tutorial shows you how to setup Eclipse to develop your AppFuse project.
In general, I don't use Eclipse for much more than a fancy text editor. I do most of my compiling, testing and deployment from the command line. This howto will hopefully make it easier for you to use Eclipse for building and testing, but if it doesn't work for you - the best fallback is to use the command line for running the Ant tasks. Of course, if you figure out ways to make the Eclipse integration better - please let me know.

This tutorial is based on Windows XP and Eclipse 3.0.1 and should work on any platform. You can download Eclipse 3.0.1 if you don't already have it installed. I also recommend downloading my Eclipse plugins bundle or purchasing MyEclipse. A pipe dream of mine is to be able to use create a MyEclipse/AppFuse project - but that would likely require an entire rewrite of AppFuse's directory structure and build file and I just don't have the time or energy. Besides, the current system works pretty well if you don't mind using Ant.

Table of Contents

Create New Java Project in Eclipse [#1]

Open Eclipse (either with an existing or a new workspace) and to go File → New → Project. Then select "Java Project" and hit the Next button. In the Project Name field, enter the name of your project (i.e. appfuse) and the directory that your project lives (i.e. c:\source\appfuse) in the box below. If you're prompted to switch the Java Perspective, choose Yes.

If you try to build the project at this point, you'll likely get numerous errors. Most of them involve the fact that the UserForm class can not be found. This is because all of the ActionForms in AppFuse (if you're using the Struts version) are generated from POJOs with XDoclet.

All of the tasks for XDoclet are configured in the Ant build.xml file so the easiest thing to do is to run "ant gen-forms" to generate the ActionForms. If you have Ant 1.6.2+ installed and in your path, you can do this from the command line. The next step shows you how configure Eclipse to run your AppFuse build.xml.

TIP: Here's a way to hide the JARs you see in Eclipse's Package Explorer.

Configuring Ant in Eclipse [#2]

The easiest way to configure Eclipse for AppFuse is to install Ant on your hard drive (i.e. c:\Tools\apache-ant-1.6.2) and then point Eclipse's ANT_HOME to this directory. To do this, go to Window → Preferences → Ant → Runtime. Then click the "Ant Home" button and select the installation folder on your hard drive.

If you'd rather use Eclipse's built-in Ant, you'll need to add junit.jar to its classpath. To do this, go to Window → Preferences → Ant → Runtime. Then click the "Add JARs" button and select junit.jar from appfuse/lib/junit3.8.1/lib/junit.jar. Click OK until you arrive back at the workbench view.

Next, add the catalina-ant.jar (from $CATALINA_HOME/server/lib) to the ant classpath. Then in the property tab, add tomcatTasks.properties (in lib/ant-contrib) file as a global properties file.

Lastly, still in Ant - Runtime - Properties tab, add the global property "tomcat.home" with a value of your CATALINA_HOME environment variable.

Below is a screenshot of what your Ant Runtime classpath should look like after the above modifications:

ant-runtime.png

Add build.xml to Ant View [#3]

Now we need to add the build.xml to Eclipse's Ant View. To do this, go to Window → Show View → Ant. Then click on the first icon in this view (screenshot below) to add AppFuse's build file.

ant-view.png

Run Ant [#4]

After adding the build.xml, you should be able to expand it in the Ant View and execute any of the targets. I usually do "test-all" to verify all the tests pass in my initial project. For a list of targets I use most often, see AppFuse Ant Targets.

Now if you run the "compile" target and then refresh the project (right-click on project → Refresh) you shouldn't see any errors in the "Problems" pane. You should now be able to compile and create classes as you normally would. Sometimes when my imports aren't resolving correctly in Eclipse, I do have to run Project → Clean in Eclipse.

NOTE: If you're using the internal version of Ant, you may get an error message like the one below:
BUILD FAILED: C:\source\appfuse\build.xml:802: The following error occurred while executing this line:
C:\source\appfuse\build.xml:780: The following error occurred while executing this line:
java.lang.NoClassDefFoundError: org/apache/xml/serialize/OutputFormat

This is because there are tasks that require Xerces to be in your Ant classpath [reference]. I added xercesImpl.jar and xml-apis.jar (from my self-installed version of Ant) to Eclipse's Ant classpath to solve this.

At this point, you should see something similar to the screenshot below.

ant-targets.png

Run JUnit Tests in Eclipse [#5]

It's also possible to run your JUnit tests in Eclipse. But before running them, you need to run the "war" target. After this target completes, refresh your project.

After you have successfully done so, in Eclipse open a test you'd like to run (i.e. UserDaoTest) and go to Run → Debug As → JUnit Test. Note that you may have to run the "db-load" target before you run your tests every so often. I did have the following method in the Base*TestCase class for each layer, but this caused DBUnit to reload the database before every test in a Test class. Removing it reduces the execution time of "test-all" by more than 30 seconds.


    protected void setUp() throws Exception {
        DataSource ds = (DataSourcectx.getBean("dataSource");
        IDatabaseConnection conn = new DatabaseConnection(ds.getConnection());
        IDataSet dataSet = 
            new XmlDataSet(new FileInputStream("metadata/sql/sample-data.xml"));
        // clear table and insert only sample data
        DatabaseOperation.CLEAN_INSERT.execute(conn, dataSet);
        conn.close();
    }

If the instructions above don't work for running JUnit tests in Eclipse, I suggest just using the command line - i.e. ant test-dao -Dtestcase=UserDAO. Running tests from the command line always works. ;-)

Tips for Debugging and UI Editing [#6]

For debugging, I use the Tomcat Plugin in Eclipse and set breakpoints. To make the breakpoints work you will need to indicate your source-path using the Eclipse menu Window->Preferences, select Tomcat, then Source Path.

For little changes, I use "ant deploy-web" which only takes a couple of seconds. For truly minor tweaks, it's sometimes easier to edit the file in Tomcat's webapps folder. For major design changes, I usually run the app, view source on a page and save it to a "sandbox" folder in the same directory as my project. Then I do a find/replace and change all "/appfuse/" references to "../web/". This allows me to change CSS and JS files and just refresh the file in the sandbox.

AppFuseOnPostgreSQL

This page describes my the steps required and issues associated with running AppFuse on PostgreSQL. If you're developing on Windows or OS X, click here for install packages.
This howto assumes you've already installed PostgreSQL and your DBA username/password combination is postgres/postgres.

Table of Contents

Setup JDBC Driver [#1]

The PostgreSQL JDBC version 3 driver is included in AppFuse, so we just need to tell AppFuse to use it, and what the database connection parameters are.

Edit your build.properties file to reflect your new database of choice. MySQL settings are the defaults specified in properties.xml. Here is a sample for PostgreSQL:

PostgreSQL:

database.jar=${postgresql.jar}
database.type=postgresql
database.name=appfuse
database.host=localhost

hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
database.driver_class=org.postgresql.Driver
database.url=jdbc:${database.type}://${database.host}/${database.name}
database.username=test
database.password=test
You can use the database.properties.reference file for other database's settings. NOTE: AppFuse cannot create your database for you in PostgreSQL like it can with MySQL. So those configuration settings are not needed. Therefore, the "db-create" target does not create a database when running PostgreSQL. See the PostgreSQL Sample Script[6] for creating a new database in the Command Line Processor.

Code/Configuration Tweaks [#2]

Using Hibernate's generator-class="native" for id's in PostgreSQL fails. This is likely due to the fact that data is being inserted using DBUnit and the sequences get out of wack. Changing all id's to "increment" (example below) and seems to fix the problem. This worked on all the databases tested (MySQL, PostgreSQL and DB2).


    /**
     * Returns the id.
     @return String
     *
     * @hibernate.id column="id"
     *  generator-class="increment" unsaved-value="null"
     */
    public Long getId() {
        return id;
    }

Run JUnit Tests [#3]

No changes are needed to make AppFuse properly run the supplied dao tests.

Configure Tomcat to talk to PostgreSQL [#4]

Since AppFuse 1.3 the database-specific attributes in tomcat-context.xml and hibernate.cfg.xml are replaced at build-time. This means that nothing special is needed to get Tomcat to talk to PostgreSQL. For users with older versions of AppFuse see the note on the DB2 version of this page for more information.

Other issues [#5]

Turn off Hibernate Batch Processing for PostgreSQL
When an exception is thrown while performing batch processing in Hibernate on PostgreSQL the cause of the exception is never displayed in the stack trace. By turning off the batch processing exceptions get reported immediately and completely. This can be done by adding an entry into applicationContext-hibernate.xml. Within the element sessionFactory, locate the property hibernateProperties and make it look like this:

        <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">@HIBERNATE-DIALECT@</prop>

            <!-- turn off batch updated for PostgreSQL to get nicer exception messages -->
	    <prop key="hibernate.jdbc.batch_size">0</prop>
        </props>
        </property>

This will have an effect on the performance of the database, so consider the tradeoff of having better error messages.

Sample CREATE DATABASE script [#6]

This is a sample PostgreSQL script to create a user and a database for an AppFuse application.
-- create the test user
create user test password 'test';

-- create the database
create database appfuse owner test;

You can find it on the sub-directory metadata/sql, with the name postgresql-create.sql. One example, suppose your project's name is felini:

	$gilberto@dev/filine: psql -d template1 -U postgres
Password:
Welcome to psql 8.1.2, the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

template1=# \imetadata/sql/postgresql-create.sql
DROP DATABASE
CREATE USER
CREATE DATABASE
template1=#

AppFuseQuickStart

NOTE: This wiki and its contents is for AppFuse 1.x. If you'd like to use AppFuse 2.x, please see the new wiki at http://appfuse.org. The QuickStart Guide for 2.x can be found at http://appfuse.org/display/APF/AppFuse+QuickStart. Thanks!

AppFuse's main purpose is to help you quickly accelerate the start of your webapp. Here are the basic steps to creating a new project with it.

  1. Install J2SE 1.4.2+ and set a JAVA_HOME environment variable pointing to your installation directory.
  2. Download the source version or checkout the 1.9.x branch from Subversion (svn co https://appfuse.dev.java.net/svn/appfuse/branches/BRANCH_1-9-x appfuse).
  3. Install Ant 1.6.5+ and set an ANT_HOME environment variable. Install Tomcat 4.1.x+ (recommend 5.5.20) and set a CATALINA_HOME environment variable to point to your Tomcat installation. Checkout my development environment setup to get links for these packages and to see where I usually install them.
  4. Install MySQL 3.23.x+ (recommend 5.0+).
    NOTE: If you're using MySQL 4.1.7, make sure to use a UTF-8 character set and an InnoDB table type. Here's how.
  5. Setup a local SMTP server or change mail.properties (in the web/WEB-INF/classes directory) and build.properties (in the root -- for log4j messages) to point to an existing one - they default to localhost.
  6. Copy lib/junit3.8.1/junit.jar to $ANT_HOME/lib.
    NOTE: You may see an ant-junit.jar file already in $ANT_HOME/lib. This jar is not the JUnit library, rather it is for the Ant junit task which will use the junit.jar that you place here.
  7. If you're planning on using iBATIS (instead of Hibernate) or a web framework other than Struts, install that now using the instructions below.
  8. Run ant new from the appfuse directory. You will be prompted for an application name, database name and package name. After entering these, a directory containing your new application will be created in the same directory as appfuse.
    WARNING: Some application values will not work - don't use "test", anything with "appfuse" in it or anything that starts with numbers. Also, two dashes (-) in a name will mess things up.
  9. Navigate to your new project's directory and run ant setup (or ant setup-db setup-tomcat deploy) to create the database, configure Tomcat and deploy your application. The database setup will only work if your root user has no password. You can change this in build.properties if necessary. Need assistance with mysql setup?
  10. If you want to test and make sure everything works, run ant test-all - make sure Tomcat is stopped when you do this. Next, run ant test-reports - there will be a message after it runs telling you how you can view the generated reports.

After you've confirmed your installation using the above steps - take a look at the Tutorials to see how to develop with AppFuse.

Optional Installations

NOTE: If you're developing with AppFuse on Unix, run "ant fixcrlf" before running the installers. You can also download a pre-built version that already has the web framework option you're looking for.

NOTE: These installers will modify the Eclipse classpath, but not the IDEA one. You'll need to modify that manually.

AppFuseSupport

This is a list of questions that relate to AppFuse. You might also checkout the AppFuse Tutorials and StrutsResume Support pages. The Mailing List Archives or the AppFuse IRC channel might also be helpful.

Table of Contents

Common Questions [#1]

The UserForm.java file is generated by XDoclet using Ant. If you run "ant compile" it gets generated into build/web/gen/org/appfuse/webapp/form.
This article explains it best.
Sure, but you'll probably get more visibility and input from the help forum or mailing list. Also, this wiki has recently been locked down b/c of spammers so you'll need to get a username/password from me to post anything. Don't forget that the mailing list archives are a great resources.
Chances are you forgot to run "ant setup-tomcat" (or "ant setup" if you need to set up the database as well). Also, if you're using Tomcat make sure you are using the latest recommended release.
Ant cannot find the ReloadTask which is in $CATALINA_HOME\server\lib\catalina-ant.jar. You need to ensure env var CATALINA_HOME (Windows dir %CATALINA_HOME% or Unix ls $CATALINA_HOME) points to the tomcat install base directory. This error can also occur when you wrap the path in double quotes, (ie. if you have set CATALINA_HOME="c:\Tomcat 5.5" then remove the double quotes!).
Ant has immutable properties. The first time a property is set, it cannot be changed. Here's the order of how properties are set in AppFuse.
command-line using -Dproperty=value
~/.appname-build.properties
~/.build.properties
build.properties
properties.xml (or build.xml) - wherever it's used''
First, instead of using all the sample-data.xml file, create another file say sample-person.xml and there put the data that you want insert. After that you can do this:
ant test-dao -Dtestcase=PersonDao -Dfile=metadata/sql/sample-person.xml

Development with AppFuse [#2]

See AppFuseAntTasks.

Security Questions [#3]

Here is how I used Andy Armstrong's JAAS Login Modules to get Tomcat to talk to an NT Domain. It's been over a year since I tried it - but it worked quite well at the time. I have no experience with jCIFS - but if you get it working - please let me know. I'd be happy to help you write a howto on this wiki for it. ~ Matt

Struts Specific [#4]

The empty keyword (pre JSP 2.0), only worked on java.util.List, java.util.Map, on Strings and arrays. Collections like java.util.Set were not supported. If you are using pre JSP 2.0 and want to test whether a Set is empty or not use: <c:if test="${mycollection['empty']}">.
The most reliable workaround to this problem I have found, is to use this notation:
  <c:forEach items="${mycollection}" var="element" varStatus="rowNum">

    <c:if test="${rowNum.count==1}">
      <table>
        <tr> <th> Value Header </th> </tr>
    </c:if>

      <tr>
        <th>
          <c:out value="${element.value}"/>
        </th>
      </tr>
      <c:if test="${rowNum.last}">
      </table>
    </c:if>
  </c:forEach>
This article explains this in more detail. If you are using tomcat 5, this supports JSP 2.0 so you will not experience this problem.

JSF Specific [#5]

This is caused by the failure of JSF to install. Start with a version that has JSF pre-installed.

Database Specific [#6]

The issue is likely because you're on Unix or you've changed the root password. See this page for assistance.
MySQL 4.1.7+ requires you to add set the default character set to UTF-8 to get unicode support with AppFuse. To do this, add the following to your c:\Windows\my.ini or /etc/my.cnf file:
[mysqld]
default-character-set=utf8
MySQL's default tables (MyISAM) do not support transactions by default. Add or replace the following line in your c:\Windows\my.ini or /etc/my.cnf file. You could also use PostgreSQL.
[mysqld]
default-storage-engine=innodb
NOTE: You may see default-storage-engine's synonym "default-table-type" in the configuration file. If so, set the value of that parameter to "innodb" instead.
BTW, MySQL Administrator is a nice tool for administering/monitoring MySQL.
Upgrading to the MySQL 3.1.5 JDBC Driver (or above) should fix this.
If a class with a natural key does not declare a version or timestamp property, it's more dificult to get saveOrUpdate() and cascades to work correctly. You might use a custom Hibernate Interceptor as discussed in this chapter. (On the other hand, if you're happy to use explicit save() and explicit update() instead of saveOrUpdate() and cascades, Hibernate doesn't need to be able to distinguish between transient and detached instances; so you can safely ignore this advice.) Composite natural keys extend the same ideas. {Hibernate in Action: pag.:333}
In metadata/conf/tomcat-context*.xml, uncomment the JNDI DataSource settings. Then in web/WEB-INF/applicationContext-resources.xml, uncomment the JNDI "dataSource" bean - and delete the active "dataSource" bean. If you decide to make this a permanent change in your project, you can delete commons-dbcp.jar and commons-pool.jar from lib/jakarta-commons.

Application Server Specific [#7]

See AppFuseOnOrion - Orion is the embedded server in Oracle AS.

Continuous Integration [#8]

Hibernate Specific Questions [#9]

See Karl Baum's writeup on Lazy Initialization. In particular, the Being Lazy in your Unit Tests section.

If you have an @hibernate.class tag on a POJO - hibernatedoclet will generate the mapping file into build/dao/gen. If you have a mapping file (*.hbm.xml) file for your POJO in the src/dao/**/model/* directory, it will overwrite the generated version. If you don't want to worry about the two conflicting - just remove the @ sign from @hibernate.class in your POJO and put your hbm.xml file in the model directory.

No build.xml modification are need for this to work. The "package-dao" target will include these mapping files:

       <copy todir="${build.dir}/dao/gen">
           <fileset dir="src/dao" includes="**/*.xml" excludes="**/*-${dao.type}.xml"/>
           <filterset refid="variables.to.replace"/>
       </copy>

If you want to get rid of the hibernatedoclet process, you can do that- but make sure and run it first - and then copy all of the generated hbm.xml files into your model directory.

Archived Questions (no longer relevant) [#10]

AppGen

AppFuse versions 1.6.1+ include an AppGen Tool that can be used to generate all the classes needed to persist a POJO. This tool was based on contributions from Lance Lavandowska and Ben Gill. At first, I didn't want to add a code-generation feature like this b/c you'd end up with a 1-to-1 relationship between tables/pojos, DAOs and Managers. On most of my projects, I have far fewer DAOs and Managers than POJOs.

By default, AppGen will generate only Actions/Controllers, Action/Controller Tests, test data, i18n keys and JSPs. It will also configure Actions/Controllers for you. It uses the generic BaseManager and BaseDaoHibernate classes (configured as "manager" and "dao") to reduce the number of files that are generated. However, I realize that sometimes you will want to generate all the DAO and Manager classes (as well as their tests), so I've added that option too.

To see what AppGen does and how it works, see the AppGen Screencast.

To use the AppGen tool (after installing your web framework), perform the following steps:

  1. If you're using Hibernate, configure the mapping file for your POJO in applicationContext-hibernate.xml. If you're using iBATIS, you can skip this step.
  2. cd into the extras/appgen directory and run ant. You will be prompted to generate from a POJO or a Table. If you choose pojo, the .java file should already exist in your model package. If you choose table, Middlegen will be used to create a POJO from an existing database table. This generates all the files you create in the tutorials on this site (for your chosen web framework).
  3. Finally, you will be asked to enter an application module or sub-package name. This is an optional feature that will allow you to organize your classes into sub-packages. For example, for a POJO "model" package of "org.appfuse.foo.model", just enter foo when prompted.
  4. To install the generated files, run ant install. You can run ant install -Dappgen.type=pojo -Dobject.name=person if you want to do everything in one fell swoop. WARNING: You might want to backup your project before you do this - or at least make sure it's checked into a source code repository. I've tested this code, and I think it works well - but it is modifying your source tree for you.

After generating the view layer, it's up to you to modify the *Form.jsp (*Form.html for Tapestry) and make it look pretty. This is covered in Step 5 of each respective web framework's "Create Action/Controller" tutorial: Struts, Spring, WebWork, JSF and Tapestry.

NOTE: If you'd like to generate all the DAOs/Managers/Tests, run ant install-detailed instead of ant install. Before you install anything, the files will be created in the extras/appgen/build/gen directory (in case you want to look at them before installing). If you just want to test the tool, you can cd to this directory and run ant test to see generate all of the code from the tutorials created.

I encourage you to read the tutorials before using AppGen. That way you'll understand what's being generated for you and you'll only need to mailing list for asking smart questions. ;-) Hopefully this tool will remove the pain of writing simple CRUD code and let you concentrate on developing your business logic and fancy UIs!

Related Tools

AppFuse Generator

Articles

Here are some articles I've written, refined or just linking to that folks seem to find useful. The latest and greatest one is Seven simple reasons to use AppFuse.

AppFuse Tutorials

If you've just downloaded AppFuse and want to setup it up on your machine, your best bet is the QuickStart Guide. Once you've got everything setup, the tutorials below are a great resource for learning how to develop with AppFuse.

NOTE: These tutorials are included in AppFuse's distribution. If you want to update your project's copy (in the docs directory), run "ant wiki".

As of 1.6.1, you can generate most of the code covered in these tutorials. If you're using Struts+Hibernate, you can generate all of it. For Spring and WebWork, it was too much trouble to write the installers so you will need to manually configure the Controllers and Actions. This was mainly due to the fact that I'm not using XDoclet for these web frameworks and the limitations of using Ant as an installer. The AppGen tool which generates the code is covered in Part I.

There's also an AppFuse Generator project that has similar functionality to AppGen.

Part I: Creating new DAOs and Objects in AppFuse - A HowTo for creating Java Objects (that represent tables) and creating Java classes to persist those objects in the database.

Translations: Chinese, German, Italian, Korean, Portuguese, Spanish

Part II: Creating new Managers - A HowTo for creating Business Facades that talk between the database tier (DAOs) and the web tier (Actions or Controllers).

Translations: Chinese, Portuguese, Spanish, Korean

Part III: (Struts) Creating Struts Actions and JSPs - A HowTo for creating Actions and JSPs in your AppFuse project. Includes generating JSPs and customizing them to look good. Also, you will write a WebTest to test the JSPs functionality. Other web framework options are as follows:

Translations: Chinese, Portuguese, Korean
Translations: Portuguese, Korean

Part IV: (Struts) Adding Validation and List Screen - Adding validation logic to the personForm so that firstName and lastName are required fields and adding a list screen to display all person records in the database.

Translations: Chinese, Portuguese, Korean
Translations: Portuguese, Korean
NOTE: You can generate the files created in these tutorials by using AppGen. If you experience problems - you should be able to compare your results to what AppGen does. To do this, cd into extras/appgen, and run "ant test-detailed". This will create an "appfuse-appgen" project, generate the code in these tutorials, and test everything.

Thomas Gaudin's Excellent AppFuse Tutorials

Thomas Gaudin has put together a couple of detailed and easy-to-follow tutorials on his site.

Related AppFuse HowTos

Translations: Portuguese, Korean
Translations: Portuguese

Server Configuration


Other

Outdated Articles that still get some traffic:

CreateActions

Part III: Creating Actions and JSPs - A HowTo for creating Struts Actions and JSPs in the AppFuse architecture.
This tutorial depends on Part II: Creating new Managers.

About this Tutorial

This tutorial will show you how to create a Struts Action, a JUnit Test (using StrutsTestCase), and a JSP for the form. The Action we create will talk to the PersonManager we created in the Creating Managers tutorial.

By default, AppFuse ships with Struts as its web framework. As of 1.6+, you can use Spring or WebWork as your web framework. In 1.7, support was added for using JSF or Tapestry.

To install any of these web frameworks instead of Struts, simply navigate to the extras directory and into the directory of the framework you want to install. The README.txt file in this directory has further instructions. The tutorials for these other frameworks are listed below.

Let's get started by creating a new Struts Action and JSP your AppFuse project.

I will tell you how I do stuff in the Real World in text like this.

Table of Contents

Add XDoclet Tags to Person to generate PersonForm [#1]

Now let's generate our PersonForm object for Struts and our web tier. To do this, we need to add XDoclet tags to the Person.java Object to create our Struts ActionForm. In the JavaDoc for the Person.java file, add the following @struts.form tag (use User.java if you need an example):


* @struts.form include-all="true" extends="BaseForm"

We extend org.appfuse.webapp.form.BaseForm because it has a toString() method that allows us to call log.debug(formName) to print out a reader-friendly view of the Form object.
If you haven't renamed the "org.appfuse" packages to "com.company" or otherwise don't have your model class in the default package, you may need to fully-qualify the reference to org.appfuse.webapp.form.BaseForm in the @struts.form tag.

Now if you run ant gen-forms, Ant (and XDoclet) will generate a PersonForm.java for you in build/web/gen/**/form.

Create skeleton JSPs using XDoclet [#2]

In this step, you'll generate a JSP page to display information from the Person object. It will contain Struts' JSP tags that render table rows for each property in Person.java. The AppGen tool that's used to do this is based off a StrutsGen tool - which was originally written by Erik Hatcher. It's basically just a couple of classes and a bunch of XDoclet templates. All these files are located in extras/appgen.

Here are the simple steps to generating the JSP and a properties file containing the labels for the form elements:

# -- person form --
personForm.id=Id
personForm.firstName=First Name
personForm.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
The files in the "pages" directory will end up in "WEB-INF/pages" at deployment time. The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from Struts' ActionServlet. Placing all JSPs below WEB-INF ensures they are only accessed through Actions, and not directly by the client or each other. This allows security to be moved up into the Action, where it can be handled more efficiently, and out of the base presentation layer.

The web application security for AppFuse specifies that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through an Action to get to a JSP (or at least the ones in pages).

NOTE: If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file. This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:
body#pageName element.class { background-color: blue } 
In the generated JSPs, there are two keys for the title (top of the browser window) and the header (heading in the page). These fields are provided above with key names of personDetail.title and personDetail.heading.

Just above, we added "personForm.*" keys to this file, so why do I use personDetail instead of personForm for the titles and headings? The best reason is because it gives a nice separation between form labels and text on the page. Another reason is because all the *Form.* give you a nice representation of all the fields in your database.

I recently had a client who wanted all fields in the database searchable. This was fairly easy to do. I just looked up all the keys in ApplicationResources.properties which contained "Form." and then put them into a drop-down. On the UI, the user was able to enter a search term and select the column they wanted to search. I was glad I followed this Form vs. Detail distinction on that project!

Create PersonActionTest to test PersonAction [#3]

To create a StrutsTestCase Test for PersonAction, start by creating a PersonActionTest.java file in the test/web/**/action directory:


package org.appfuse.webapp.action;

import org.appfuse.Constants;
import org.appfuse.webapp.form.PersonForm;

public class PersonActionTest extends BaseStrutsTestCase {
    
    public PersonActionTest(String name) {
        super(name);
    }

    public void testEdit() throws Exception {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Edit");
        addRequestParameter("id""1");
        actionPerform();

        verifyForward("edit");
        assertTrue(request.getAttribute(Constants.PERSON_KEY!= null);
        verifyNoActionErrors();
    }

    public void testSave() throws Exception {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Edit");
        addRequestParameter("id""1");

        actionPerform();

        PersonForm personForm =
            (PersonFormrequest.getAttribute(Constants.PERSON_KEY);
        assertTrue(personForm != null);
        
        setRequestPathInfo("/savePerson");
        addRequestParameter("method""Save");

        // update the form from the edit and add it back to the request
        personForm.setLastName("Feltz");
        request.setAttribute(Constants.PERSON_KEY, personForm);

        actionPerform();

        verifyForward("edit");
        verifyNoActionErrors();
    }

    public void testRemove() throws Exception {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Delete");
        addRequestParameter("id""2");
        actionPerform();

        verifyForward("mainMenu");
        verifyNoActionErrors();
    }
}

You will need to add PERSON_KEY as a variable to the src/dao/**/Constants.java class. The name, "personForm", matches the name given to the form in the struts-config.xml file.


    /**
     * The request scope attribute that holds the person form.
     */
    public static final String PERSON_KEY = "personForm";

If you try to run this test, you will get a number of NoSuchMethodErrors - so let's define the edit, save, and delete methods in the PersonAction class.

Create PersonAction [#4]

In src/web/**/action, create a PersonAction.java file with the following contents:


package org.appfuse.webapp.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;
import org.appfuse.webapp.form.PersonForm;

/**
 * @struts.action name="personForm" path="/editPerson" scope="request"
 *  validate="false" parameter="method" input="mainMenu"
 */
public final class PersonAction extends BaseAction {
    
    public ActionForward cancel(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
    throws Exception {
        return mapping.findForward("mainMenu");
    }

    public ActionForward delete(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'delete' method");
        }

        ActionMessages messages = new ActionMessages();
        PersonForm personForm = (PersonFormform;

        // Exceptions are caught by ActionExceptionHandler
        PersonManager mgr = (PersonManagergetBean("personManager");
        mgr.removePerson(personForm.getId());

        messages.add(ActionMessages.GLOBAL_MESSAGE,
                     new ActionMessage("person.deleted"));

        // save messages in session, so they'll survive the redirect
        saveMessages(request.getSession(), messages);

        return mapping.findForward("mainMenu");
    }

    public ActionForward edit(ActionMapping mapping, ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'edit' method");
        }

        PersonForm personForm = (PersonFormform;

        // if an id is passed in, look up the user - otherwise
        // don't do anything - user is doing an add
        if (personForm.getId() != null) {
            PersonManager mgr = (PersonManagergetBean("personManager");
            Person person = mgr.getPerson(personForm.getId());
            personForm = (PersonFormconvert(person);
            updateFormBean(mapping, request, personForm);
        }

        return mapping.findForward("edit");
    }

    public ActionForward save(ActionMapping mapping, ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'save' method");
        }

        // Extract attributes and parameters we will need
        ActionMessages messages = new ActionMessages();
        PersonForm personForm = (PersonFormform;
        boolean isNew = ("".equals(personForm.getId()));

        if (log.isDebugEnabled()) {
            log.debug("saving person: " + personForm);
        }

        PersonManager mgr = (PersonManagergetBean("personManager");
        Person person = (Personconvert(personForm);
        mgr.savePerson(person);

        // add success messages
        if (isNew) {
            messages.add(ActionMessages.GLOBAL_MESSAGE,
                         new ActionMessage("person.added"));

            // save messages in session to survive a redirect
            saveMessages(request.getSession(), messages);

            return mapping.findForward("mainMenu");
        else {
            messages.add(ActionMessages.GLOBAL_MESSAGE,
                         new ActionMessage("person.updated"));
            saveMessages(request, messages);

            return mapping.findForward("edit");
        }
    }
}

You'll notice in the code above that there are many calls to to convert a PersonForm or a Person object. The convert method is in BaseAction.java (which calls ConvertUtil.convert()) and uses BeanUtils.copyProperties to convert POJOs → ActionForms and ActionForms → POJOs.

If you are running Eclipse, you might have to "refresh" the project in order to see PersonForm. It lives in build/web/gen, which should be one of your project's source folders. This is the only way for Eclipse to see and import PersonForm, since it is generated by XDoclet and does not live in your regular source tree. You can find it at build/web/gen/org/appfuse/webapp/form/PersonForm.java.
You can also configure Eclipse to auto-refresh your workspace using: Window > Preferences > General > Workspace > Refresh Automatically.
In BaseAction you can register additional Converters (i.e. DateConverter) so that BeanUtils.copyProperties knows how to convert Strings → Objects. If you have Lists on your POJOs (i.e. for parent-child relationships), you will need to manually convert those using the convertLists(java.lang.Object) method.

Now you need to add the edit forward and the savePerson action-mapping, both which are specified in in the PersonActionTest. To do this, add a couple more XDoclet tags to the top of the PersonAction.java file. Do this right above the class declaration. You should already have the XDoclet tag for the editPerson action-mapping, but I'm showing it here so you can see all the XDoclet tags at the top of this class.


/**
 * @struts.action name="personForm" path="/editPerson" scope="request"
 *  validate="false" parameter="method" input="mainMenu"
 
 * @struts.action name="personForm" path="/savePerson" scope="request"
 *  validate="true" parameter="method" input="edit"
 
 * @struts.action-forward name="edit" path="/WEB-INF/pages/personForm.jsp"
 */
public final class PersonAction extends BaseAction {

The main difference between the editPerson and savePerson action-mappings is that savePerson has validation turned on (see validation="true") in the XDoclet tag above. Note that the "input" attribute must refer to a forward, and cannot be a path (i.e. /editPerson.html). If you'd prefer to use the save path for both edit and save, that's possible too. Just make sure validate="false", and then in your "save" method - you'll need to call form.validate() and handle errors appropriately.

You might notice that the code you're using to call the PersonManager is the same as the code used in the PersonManagerTest. Both PersonAction and PersonManagerTest are clients of PersonManagerImpl, so this makes perfect sense.

Everything is almost done for this tutorial, let's get to running the tests!

Run PersonActionTest [#5]

If you look at our PersonActionTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so add that to our sample data file (metadata/sql/sample-data.xml). I'd add it at the bottom - order is not important since it (currently) does not relate to any other tables.

  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

DBUnit loads this file before we run any of our tests, so this record will be available to the PersonActionTest.

Now if you run ant test-web -Dtestcase=PersonAction - everything should work as planned. Make sure Tomcat isn't running before you try this.

BUILD SUCCESSFUL
Total time: 1 minute 21 seconds

Clean up the JSP to make it presentable [#6]

Now let's clean up the generated personForm.jsp. Change the action of the <html:form> to be "savePerson" so validation will be turned on when saving. Also, change the focus attribute from focus="" to focus="firstName" so the cursor will be in the firstName field when the page loads (this is done with JavaScript).

Another thing you will need to do is comment out the following lines at the bottom of the personForm.jsp. This is because the Validator will throw an exception if a formName is specified and no validation rules exist for it.

Personally, I think this is a bug, but the Struts Committers disagreed.
<html:javascript formName="personForm" cdata="false"
    dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
    src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

Now if you execute ant db-load deploy, start Tomcat and point your browser to http://localhost:8080/appfuse/editPerson.html?id=1, you should see something like this:

personForm-final.png

NOTE: Use the deploy-web target if you've changed any files under the web directory. Otherwise, use deploy which compiles and deploys.

Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, which can easily be done by adding text (using <fmt:message>) at the top of the personForm.jsp page.

[Optional] Create a Canoo WebTest to test browser-like actions [#7]

The final (optional) step in this tutorial is to create a Canoo WebTest to test the JSPs.
I say this step is optional, because you can run the same tests through your browser.

You can use the following URLs to test the different actions for adding, editing and saving a user.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.

I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing -Dtestcase=Name, I've found that I'm used to doing CamelCase for my JUnit Tests.


<!-- runs person-related tests -->
<target name="PersonTests"
    depends="EditPerson,SavePerson,AddPerson,DeletePerson"
    description="Call and executes all person test cases (targets)">
    <echo>Successfully ran all Person JSP tests!</echo>
</target>

<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson"
    description="Tests editing an existing Person's information">
    <webtest name="editPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
        </steps>
    </webtest>
</target>

<!-- Edit a person and then save -->
<target name="SavePerson"
    description="Tests editing and saving a user">
    <webtest name="savePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set lastName" name="lastName" value="Canoo"/>
            <clickbutton label="Save" description="Click Save"/>
            <verifytitle description="Page re-appears if save successful"
                text=".*${personDetail.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.updated}"/>
        </steps>
    </webtest>
</target>

<!-- Add a new Person -->
<target name="AddPerson"
    description="Adds a new Person">
    <webtest name="addPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Add Button" url="/editPerson.html"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set firstName" name="firstName" value="Abbie"/>
            <setinputfield description="set lastName" name="lastName" value="Raible"/>
            <clickbutton label="${button.save}" description="Click button 'Save'"/>
            <verifytitle description="Main Menu appears if save successful"
                text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.added}"/>
        </steps>
    </webtest>
</target>

<!-- Delete existing person -->
<target name="DeletePerson"
    description="Deletes existing Person">
    <webtest name="deletePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
            <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
            <verifyNoDialogResponses/>
            <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.deleted}"/>
        </steps>
    </webtest>
</target>

After adding this, you should be able to run ant test-canoo -Dtestcase=PersonTests with Tomcat running or ant test-jsp -Dtestcase=PersonTests if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.

You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add the following between </webtest> and </target> at the end of each target.

<loadfile property="web-tests.result" 
    srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>
BUILD SUCCESSFUL
Total time: 11 seconds


Next Up: Part IV: Adding Validation and List Screen - Adding validation logic to the personForm so that firstName and lastName are required fields and adding a list screen to display all person records in the database.

CreateDAO

Part I: Creating new DAOs and Objects in AppFuse - A HowTo for creating Java Objects (that represent tables) and creating Java classes to persist those objects in the database.

About this tutorial

This tutorial will show you how to create a new table in the database, and how the create Java code to access this table.

You will create an object and then some more classes to persist (save/retrieve/delete) that object from the database. In Java speak, this object is called a Plain Old Java Object (a.k.a. a POJO). This object basically represents a database table. The other classes will be:

AppFuse uses Hibernate for its default persistence layer. Hibernate is an Object/Relational (O/R) Framework that allows you to relate your Java Objects to database tables. It allows you to very easily perform CRUD (Create, Retrieve, Update, Delete) on your objects.

You can also use iBATIS as a persistence framework option. To install iBATIS in AppFuse, view the README.txt in extras/ibatis. Then complete the iBATIS version of this tutorial.

Font Conventions (work in progress)

Literal strings intended to be executed at the command prompt look like this: ant test-all.
References to files, directories and packages which exist in your source tree: build.xml.
And suggestions for how to do stuff in the "Real World" are in blue italics.

Let's get started on creating a new Object, DAO and Test in AppFuse's project structure.

Table of Contents

Create a new Object and add XDoclet tags [#1]

The first thing you need to do is create an object to persist. Create a simple "Person" object (in the src/dao/**/model directory) that has an id, a firstName and a lastName (as properties).

NOTE: Copying the Java code in these tutorials doesn't work in Firefox. A workaround is to CTRL+Click (Command+Click on OS X) the code block and then copy it.


package org.appfuse.model;

public class Person extends BaseObject {
    private Long id;
    private String firstName;
    private String lastName;

    /*
     Generate your getters and setters using your favorite IDE: 
     In Eclipse:
     Right-click -> Source -> Generate Getters and Setters
    */
}

This class should extend BaseObject, which has 3 abstract methods: (equals(), hashCode() and toString()) that you will need to implement in the Person class. The first two are required by Hibernate. If you plan to put this object into the user's session, or expose it through a web service, you should implement java.io.Serializable as well.

The easiest way to do this is using Commonclipse. More information on using this tool can be found on Lee Grey's site. Another Eclipse Plugin you can use is Commons4E. I haven't used it, so I can't comment on its functionality.

If you're using IntelliJ IDEA, you can generate equals() and hashCode(), but not toString(). There is a ToStringPlugin that works reasonably well.
NOTE: If installing these plugins doesn't work for you, you can find all of these methods in the Person.java object that's used to test AppGen. Just look in extras/appgen/test/dao/org/appfuse/model/Person.java and copy and paste the methods from that class.

Now that you have this POJO created, you need to add XDoclet tags to generate the Hibernate mapping file. This mapping file is used by Hibernate to map objects → tables and properties (variables) → columns.

First of all, add a @hibernate.class tag that tells Hibernate what table this object relates to:


/**
 * @hibernate.class table="person"
 */
public class Person extends BaseObject {

You also have to add a primary key mapping or XDoclet will puke when generating the mapping file. Note that all @hibernate.* tags should be placed in the getters' Javadocs of your POJOs.


    /**
     @return Returns the id.
     * @hibernate.id column="id" generator-class="increment" unsaved-value="null"
     */

    public Long getId() {
        return this.id;
    }

I'm using generator-class="increment" instead of generator-class="native" because I found some issues when using "native" on other databases. If you only plan on using MySQL, I recommend you use the "native" value. This tutorial uses increment.

Create a new database table from the object using Ant [#2]

At this point, you can create the person table by running ant setup-db. This task creates the Person.hbm.xml file and creates a database table called "person". From the ant console, you can see the table schema the Hibernate creates for you:
[schemaexport] create table person (
[schemaexport]    id bigint not null,
[schemaexport]    primary key (id)
[schemaexport] );

If you want to look at the Person.hbm.xml file that Hibernate generates for you, look in the build/dao/gen/**/model directory. Here's the contents of Person.hbm.xml (so far):


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class
        name="org.appfuse.model.Person"
        table="person"
        dynamic-update="false"
        dynamic-insert="false"
    >

        <id
            name="id"
            column="id"
            type="java.lang.Long"
            unsaved-value="null"
        >
            <generator class="increment">
            </generator>
        </id>

        <!--
            To add non XDoclet property mappings, create a file named
                hibernate-properties-Person.xml
            containing the additional properties and place it in your merge dir.
        -->

    </class>

</hibernate-mapping>

Now you'll add additional @hibernate.property tags for the other columns (first_name, last_name):


    /**
     * @hibernate.property column="first_name" length="50"
     */
    public String getFirstName() {
        return this.firstName;
    }

    /**
     * @hibernate.property column="last_name" length="50"
     */
    public String getLastName() {
        return this.lastName;
    }

In this example, the only reason for adding the column attribute is because the column name is different from the property name. If they're the same, you don't need to specify the column attribute. See the @hibernate.property reference for other attributes you can specify for this tag.

Run ant setup-db again to get the additional columns added to your table.

[schemaexport] create table person (
[schemaexport]    id bigint not null,
[schemaexport]    first_name varchar(50),
[schemaexport]    last_name varchar(50),
[schemaexport]    primary key (id)
[schemaexport] );

If you want to change the size of your columns, modify the length attribute in your @hibernate.property tag. If you want to make it a required field (NOT NULL), add not-null="true".

Create a new DaoTest to run JUnit tests on your DAO [#3]

NOTE: AppFuse versions 1.6.1+ contain include an AppGen tool that can be used to generate all the classes for the rest of these tutorials. However, it's best that you go through these tutorials before using this tool - then you'll know what code it's generating.

Now you'll create a DaoTest to test that your DAO works. "Wait a minute," you say, "I haven't created a DAO!" You are correct. However, I've found that Test-Driven Development breeds higher quality software. For years, I thought write your test before your class was hogwash. It just seemed stupid. Then I tried it and I found that it works great. The only reason I do all this test-driven stuff now is because I've found it rapidly speeds up the process of software development.

To start, create a PersonDaoTest.java class in the test/dao/**/dao directory. This class should extend BaseDaoTestCase, a subclass of Spring's AbstractTransactionalDataSourceSpringContextTests which already exists in this package. This parent class is used to load Spring's ApplicationContext (since Spring binds the layers together), and for (optionally) loading a .properties file (ResourceBundle) that has the same name as your *Test.class. In this example, if you put a PersonDaoTest.properties file in the same directory as PersonDaoTest.java, this file's properties will be available via an "rb" variable.


package org.appfuse.dao;

import org.appfuse.model.Person;
import org.springframework.dao.DataAccessException;

public class PersonDaoTest extends BaseDaoTestCase {
    
    private Person person = null;
    private PersonDao dao = null;

    public void setPersonDao(PersonDao dao) {
        this.dao = dao;
    }
}

The code you see above is what you need for a basic Spring integration test that initializes and configures an implementation of PersonDao. Spring will use autowiring byType to call the setPersonDao() method and set the "personDao" bean as a dependency of this class.

Now you need test that the CRUD (create, retrieve, update, delete) methods work in your DAO. To do this, create methods that begin with "test" (all lower case). As long as these methods are public, have a void return type and take no arguments, they will be called by the <junit> task in build.xml. Below are some simple tests for testing CRUD. An important thing to remember is that each method (also known as a test), should be autonomous. Add the following methods to your PersonDaoTest.java file:


    public void testGetPerson() throws Exception {
        person = new Person();
        person.setFirstName("Matt");
        person.setLastName("Raible");

        dao.savePerson(person);
        assertNotNull(person.getId());

        person = dao.getPerson(person.getId());
        assertEquals(person.getFirstName()"Matt");
    }

    public void testSavePerson() throws Exception {
        person = dao.getPerson(new Long(1));
        person.setFirstName("Matt");

        person.setLastName("Last Name Updated");

        dao.savePerson(person);

        if (log.isDebugEnabled()) {
            log.debug("updated Person: " + person);
        }

        assertEquals(person.getLastName()"Last Name Updated");
    }

    public void testAddAndRemovePerson() throws Exception {
        person = new Person();
        person.setFirstName("Bill");
        person.setLastName("Joy");

        dao.savePerson(person);

        assertEquals(person.getFirstName()"Bill");
        assertNotNull(person.getId());

        if (log.isDebugEnabled()) {
            log.debug("removing person...");
        }

        dao.removePerson(person.getId());

        try {
            person = dao.getPerson(person.getId());
            fail("Person found in database");
        catch (DataAccessException dae) {
            log.debug("Expected exception: " + dae.getMessage());
            assertNotNull(dae);
        }
    }

In the testGetPerson method, you're creating a person and then calling a get. I usually enter a record in the database that I can always rely on. Since DBUnit is used to populate the database with test data before the tests are run, you can simply add the new table/record to the metadata/sql/sample-data.xml file:

<table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
</table>
This way, you can eliminate the "create new" functionality in the testGetPerson method. If you'd rather add this record directly into the database (via SQL or a GUI), you can rebuild your sample-data.xml file using ant db-export and then cp db-export.xml metadata/sql/sample-data.xml.

In the above example, you can see that person.set*(value) is being called to populate the Person object before saving it. This is easy in this example, but it could get quite cumbersome if you're persisting an object with 10 required fields (not-null="true"). This is why I created the ResourceBundle in the BaseDaoTestCase. Simply create a PersonDaoTest.properties file in the same directory as PersonDaoTest.java and define your property values inside it:

I tend to just hard-code test values into Java code - but the .properties file is an option that works great for large objects.
firstName=Matt
lastName=Raible
Then, rather than calling person.set* to populate your objects, you can use the BaseDaoTestCase.populate(java.lang.Object) method:


person = new Person();
person = (Personpopulate(person);

At this point, the PersonDaoTest class won't compile yet because there is no PersonDao.class in your classpath, you need to create it. PersonDao.java is an interface, and PersonDaoHibernate.java is the Hibernate implementation of that interface.

Create a new DAO to perform CRUD on the object [#4]

First off, create a PersonDao.java interface in the src/dao/**/dao directory and specify the basic CRUD methods for any implementation classes.


package org.appfuse.dao;

import org.appfuse.model.Person;

public interface PersonDao extends Dao {
    public Person getPerson(Long personId);
    public void savePerson(Person person);
    public void removePerson(Long personId);
}

Notice in the class above there are no exceptions on the method signatures. This is due to the power of Spring and how it wraps Exceptions with RuntimeExceptions. At this point, you should be able to compile all the source in src/dao and test/dao using ant compile-dao. However, if you try to run ant test-dao -Dtestcase=PersonDao, you will get an error: No bean named 'personDao' is defined. This is an error message from Spring - indicating that you need to specify a bean named personDao in applicationContext-hibernate.xml. Before you do that, you need to create the PersonDao implementation class.

The ant task for running dao tests is called test-dao. If you pass in a testcase parameter (using -Dtestcase=name), it will look for **/*${testcase}* - allowing us to pass in Person, PersonDao, or PersonDaoTest - all of which will execute the PersonDaoTest class.

Let's start by creating a PersonDaoHibernate class that implements the methods in PersonDao and uses Hibernate to get/save/delete the Person object. To do this, create a new class in src/dao/**/dao/hibernate and name it PersonDaoHibernate.java. It should extend BaseDaoHibernate and implement PersonDao. Javadocs eliminated for brevity.


package org.appfuse.dao.hibernate;

import org.appfuse.model.Person;
import org.appfuse.dao.PersonDao;
import org.springframework.orm.ObjectRetrievalFailureException;

public class PersonDaoHibernate extends BaseDaoHibernate implements PersonDao {

    public Person getPerson(Long id) {
        Person person = (PersongetHibernateTemplate().get(Person.class, id);

        if (person == null) {
            throw new ObjectRetrievalFailureException(Person.class, id);
        }

        return person;
    }

    public void savePerson(Person person) {
        getHibernateTemplate().saveOrUpdate(person);
    }

    public void removePerson(Long id) {
        // object must be loaded before it can be deleted
        getHibernateTemplate().delete(getPerson(id));
    }
}

Now, if you try to run ant test-dao -Dtestcase=PersonDao, you will get the same error. We need to configure Spring so it knows that PersonDaoHibernate is the implementation of PersonDao, and you also need to tell it about the Person object.

Configure Spring for the Person object and PersonDao [#5]

First, you need to tell Spring where the Hibernate mapping file is located. To do this, open src/dao/**/dao/hibernate/applicationContext-hibernate.xml and add "Person.hbm.xml" to the following code block.


<property name="mappingResources"
    <list> 
        <value>org/appfuse/model/Person.hbm.xml</value> 
        <value>org/appfuse/model/Role.hbm.xml</value> 
        <value>org/appfuse/model/User.hbm.xml</value>
    </list> 
</property> 

Now you need to add some XML to this file to bind PersonDaoHibernate to PersonDao. To do this, add the following at the bottom of the file:


<!-- PersonDao: Hibernate implementation --> 
<bean id="personDao" class="org.appfuse.dao.hibernate.PersonDaoHibernate"
    <property name="sessionFactory" ref="sessionFactory"/>
</bean> 

NOTE: Don't forget to correct the package name in the "class" attribute above if you specified a package other then org.appfuse when you created your project.

Run the DaoTest [#6]

Save all your edited files and try running ant test-dao -Dtestcase=PersonDao one more time.

Yeah Baby, Yeah: BUILD SUCCESSFUL
Total time: 9 seconds


Next Up: Part II: Creating new Managers - A HowTo for creating Business Facades, which are similar to Session Facades, but don't use EJBs. These facades are used to provide communication from the front-end to the DAO layer.

CreateManager

Part II: Creating new Managers - A HowTo for creating Business Facades that talk to the database tier (DAOs) and handle transaction management.
This tutorial depends on Part I: Creating new DAOs and Objects in AppFuse.

About this Tutorial

This tutorial will show you how to create a Business Facade class (and a JUnit Test) to talk to the DAO we created in Part I.

In the context of AppFuse, this is called a Manager class. Its main responsibility is to act as a bridge between the persistence (DAO) layer and the web layer. It's also useful for de-coupling your presentation layer from your database layer (i.e. for Swing apps). Managers should also be where you put any business logic for your application.

I will tell you how I do stuff in the Real World in text like this.

Let's get started by creating a new ManagerTest and Manager in AppFuse's architecture.

Table of Contents

Create a new ManagerTest to run JUnit tests on the Manager [#1]

In Part I, we created a Person object and PersonDao - so let's continue developing this entity. First, let's create a JUnit test for the PersonManager. Create PersonManagerTest in the test/service/**/service directory. We'll want to test the same basic methods (get, save, remove) that our DAO has.
This may seem redundant (why all the tests!), but these tests are GREAT to have 6 months down the road.

This class should extend BaseManagerTestCase, which already exists in the service package. The parent class (BaseManagerTestCase) serves similar functionality as the BaseDaoTestCase.

I usually copy (open → save as) an existing test (i.e. UserManagerTest.java) and find/replace [Uu]ser with [Pp]erson, or whatever the name of my object is.

The code below is what you need for a basic JUnit test of your Manager. Unlike the DaoTest, this test uses jMock to isolate the Manager from its dependencies and make it a true "unit" test. This can be very helpful because it allows you to test your business logic w/o worrying about other dependencies. The code below simply sets up the Manger and its dependencies (as Mocks) for testing.


package org.appfuse.service;

import java.util.List;
import java.util.ArrayList;

import org.appfuse.dao.PersonDao;
import org.appfuse.model.Person;
import org.appfuse.service.impl.PersonManagerImpl;

import org.jmock.Mock;
import org.springframework.orm.ObjectRetrievalFailureException;

public class PersonManagerTest extends BaseManagerTestCase {
    private final String personId = "1";
    private PersonManager personManager = new PersonManagerImpl();
    private Mock personDao = null;
    private Person person = null;

    protected void setUp() throws Exception {
        super.setUp();
        personDao = new Mock(PersonDao.class);
        personManager.setPersonDao((PersonDaopersonDao.proxy());
    }

    protected void tearDown() throws Exception {
        super.tearDown();
        personManager = null;
    }
}

Now that you have the skeleton done for this class, you need to add the meat: the test methods to make sure everything works. Here's a snippet from the DAO Tutorial tutorial to help you understand what we're about to do.

...we create methods that begin with "test" (all lower case). As long as these methods are public, have a void return type and take no arguments, they will be called by our <junit> task in our Ant build.xml file. Here's some simple tests for testing CRUD. An important thing to remember is that each method (also known as a test), should be autonomous.

Add the following methods to your PersonManagerTest.java file:


    public void testGetPerson() throws Exception {
        // set expected behavior on dao
        personDao.expects(once()).method("getPerson")
            .will(returnValue(new Person()));
        person = personManager.getPerson(personId);
        assertTrue(person != null);
        personDao.verify();
    }

    public void testSavePerson() throws Exception {
        // set expected behavior on dao
        personDao.expects(once()).method("savePerson")
            .with(same(person)).isVoid();

        personManager.savePerson(person);
        personDao.verify();
    }    

    public void testAddAndRemovePerson() throws Exception {
        person = new Person();

        // set required fields
        person.setFirstName("firstName");
        person.setLastName("lastName");

        // set expected behavior on dao
        personDao.expects(once()).method("savePerson")
            .with(same(person)).isVoid();
        personManager.savePerson(person);
        personDao.verify();

        // reset expectations
        personDao.reset();

        personDao.expects(once()).method("removePerson").with(eq(new Long(personId)));
        personManager.removePerson(personId);
        personDao.verify();

        // reset expectations
        personDao.reset();
        // remove
        Exception ex = new ObjectRetrievalFailureException(Person.class, person.getId());
        personDao.expects(once()).method("removePerson").isVoid();            
        personDao.expects(once()).method("getPerson").will(throwException(ex));
        personManager.removePerson(personId);
        try {
            personManager.getPerson(personId);
            fail("Person with identifier '" + personId + "' found in database");
        catch (ObjectRetrievalFailureException e) {
            assertNotNull(e.getMessage());
        }
        personDao.verify();
    }

This class won't compile at this point because we have not created our PersonManager interface.

I think it's funny how I've followed so many patterns to allow extendibility in AppFuse. In reality, on most projects I've been on - I learn so much in a year that I don't want to extend the architecture - I want to rewrite it. Hopefully by keeping AppFuse up to date with my perceived best practices, this won't happen as much. Each year will just be an upgrade to the latest AppFuse, rather than a re-write. ;-)

Create a new Manager to talk to the DAO [#2]

First off, create a PersonManager.java interface in the src/service/**/service directory and specify the basic CRUD methods for any implementation classes. I've eliminated the JavaDocs in the class below for display purposes. The setPersonDao() method is not used in most cases - its just exists so the PersonManagerTest can set the DAO on the interface.

As usual, I usually duplicate (open → save as) an existing file (i.e. UserManager.java).


package org.appfuse.service;

import org.appfuse.model.Person;
import org.appfuse.dao.PersonDao;

public interface PersonManager {
    public void setPersonDao(PersonDao dao);
    public Person getPerson(String id);
    public void savePerson(Person person);
    public void removePerson(String id);
}

Now let's create a PersonManagerImpl class that implements the methods in PersonManager. To do this, create a new class in src/service/**/service/impl and name it PersonManagerImpl.java. It should extend BaseManager and implement PersonManager.


package org.appfuse.service.impl;

import org.appfuse.model.Person;
import org.appfuse.dao.PersonDao;
import org.appfuse.service.PersonManager;

public class PersonManagerImpl extends BaseManager implements PersonManager {
    private PersonDao dao;

    public void setPersonDao(PersonDao dao) {
        this.dao = dao;
    }

    public Person getPerson(String id) {
        return dao.getPerson(Long.valueOf(id));
    }

    public void savePerson(Person person) {
        dao.savePerson(person);
    }

    public void removePerson(String id) {
        dao.removePerson(Long.valueOf(id));
    }
}

One thing to note is the setPersonDao() method. This is used by Spring to bind the PersonDao to this Manager. This is configured in the applicationContext-service.xml file. We'll get to configuring that in Step 3[3]. You should be able to compile everything now using "ant compile-service".

Now you need to edit Spring's config file for our services layer so it will know about this new Manager.

Configure Spring for this Manager and Transactions [#3]

To notify Spring of this our PersonManager interface and its implementation, open the src/service/**/service/applicationContext-service.xml file. Add the following to the bottom of this file.


    <bean id="personManager" class="org.appfuse.service.impl.PersonManagerImpl">
        <property name="personDao" ref="personDao"/>
    </bean>

This bean must have a name that ends in "Manager". This is because there is AOP advice applied at the top of this file for all *Manager beans.

<aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service.*Manager.*(..))" order="2"/>
For more information on transactions with Spring, see Spring's documentation.

Run the ManagerTest [#4]

Save all your edited files and try running ant test-service -Dtestcase=PersonManager.

Yeah Baby, Yeah: BUILD SUCCESSFUL
Total time: 9 seconds


The files that were modified and added to this point are available for download.

Next Up: Part III: Creating Actions and JSPs - A HowTo for creating Actions and JSPs in the AppFuse architecture.

DevelopmentEnvironment

This page describes how to setup your development environment to compile/deploy AppFuse or StrutsResume from the command line. After setting this up, you might want to checkout HowTo Run Ant in Eclipse. This is how I currently have things setup and what I advise clients to use when we use AppFuse as a baseline for webapp development.

Table of Contents

Download [#1]

  1. Download the latest JDK (J2SE SDK) from http://java.sun.com. As of 07/12/2005, this is 1.4.2_08. AppFuse should work with 1.5 if you'd like to use that. Here's my experience. Note that Cactus has been removed from AppFuse in 1.6 and Cargo (used for testing JSPs) doesn't work with Tomcat 5.5.x yet.
  2. Download the latest Tomcat release from http://jakarta.apache.org/tomcat. At the time of this writing, it's 5.0.28. DON'T get the LE version or you'll have to add DBCP (database connection pool) and JavaMail (for e-mail) JARs.
  3. Download the latest Ant release from http://ant.apache.org. AppFuse 1.6+ requires 1.6.2 or greater.
  4. Download the latest MySQL release from http://www.mysql.com. Currently, this is 4.0.21.

I usually put all these downloads in a "Downloads" folder - in fact, I plan on starting to pack around a CD with all of these libraries on them - nice to have when traveling to new clients and networks are slow.

Install [#2]

Make sure you have WinZip installed (for Windows) or gnutar for OS X before installing these packages.
  1. Create a "Tools" and "SDKs" folder on your hard drive. On Windows, I create these at c:\Tools and c:\SDKs. On *nix, I usually do /opt/dev/tools and opt/dev/sdks.
  2. Create Environment variables for these folders - SDKS_HOME and TOOLS_HOME (optional)
  3. Install the J2SE SDK (a.k.a. JDK) in the SDKs directory - keeping the directory names intact.
  4. Install Tomcat in the Tools directory - I usually name the install directory "jakarta-tomcat-x" where x is the current version (i.e. 5.0.28).
  5. Unzip/Install Ant in the Tools directory - "apache-ant-x" is what I use for the directory name.
  6. Install MySQL in the Tools directory. I usually just leave it named "mysql".
  7. Create a "Source" directory on your hard drive (this is where we'll put all the source code for our projects). On *nix, I usually create a "dev" folder in my home directory.

At this point, you should have a directory structure that looks something like the following:

SDKs -
    - j2sdk-1.4.2_05
Tools - 
    - apache-ant-1.6.2
    - jakarta-tomcat-5.0.28
    - mysql
Source

Now we'll configure all these tools so that your Operating System knows they're installed.

Configure [#3]

I'll only show a Windows example and I'll assume the *nix folks are smart enough to figure it out for their system.

  1. To set Environment Variables in Windows, either go to Control Panel -> System or right-click My Computer -> Properties.
  2. Click on the Advanced Tab and then click the Environment Variables button.
  3. Put focus on the second box (System Variables) by clicking on one of the existing values.
  4. Enter the following variables:

You should now be able to open a command prompt and type "java -version", "ant -version" or "mysql" and not get errors.

Additional Tips [#4]

alias ls="ls -CF --color"
alias ll='ls -la'
alias tstart=$CATALINA_HOME/bin/startup.bat
alias tstop=$CATALINA_HOME/bin/shutdown.bat

If you're starting work at a new client, I also recommend you do the following to help your development process become more efficient. Most of my clients in the last couple years have not had these in place, that's why I recommend them here.

  1. Setup a source control system. I prefer CVS because I'm most familiar with it. A nice addon is CVS Spam for HTML-formatted check-in notifications.
  2. Setup a bug tracking system. Popular (free) choices are Bugzilla and Scarab. The best one I've seen is JIRA (demo), but I've yet to convince a client to shell out the $800 for it.
  3. Setup a Wiki. My favorite is JSPWiki - which is what this site uses. You can also download this template if you like.
  4. Setup a development box to host the source control system, the bug tracking system, and a wiki. Install Tomcat on this box and Anthill for automated testing (I run both AppFuse and Struts-Resume on Anthill at home).
  5. (optional) Install Roller and use it to report your daily status and issues. This will allow your client (or supervisor) to track your progress.

HibernateRelationships

About this tutorial

This is a tutorial to show how to create and manage Hibernate relationships within AppFuse. This tutorial was written using AppFuse 1.8.2. All of the code for this tutorial is downloadable at http://static.appfuse.org/downloads/appfuse-hr.zip.
For further details about the specifics regarding creating objects, XDoclet tags for Hibernate, DAO development, etc., please refer to Creating new DAOs and Objects in AppFuse.
NOTE: Copying the Java code in this tutorials doesn't work in Firefox. A workaround is to CTRL+Click (Command+Click on OS X) the code block and then copy it.

Table of Contents

Create Weblog.java, Entry.java and add XDoclet tags [#1]

The Weblog object is used to indentify a person's blog. This class has the following properties:

The Entry object is used to contain a listing of a person's blog entries in their Weblog. This class contains the following properties:

NOTE: The primary keys are prefixed with their entity name to avoid confusion. I generally recommend using "id" for your entities, but wanted to make thing clearer in this tutorial.

Below is a class diagram of these two objects, as well as the others you'll create in this tutorial.

ER-Diagram.jpg

The first thing you need to do in this tutorial is these two object to persist. Create a Weblog.java class and an Entry.java class (in the src/dao/**/model directory). The necessary XDoclet tags for these entities is included on the getter method's javadoc. You can download these files using the links below. Note that javadocs have been eliminated for brevity.

Rather than fill up this tutorial with large blocks of Java code, the necessary files are attached and linked to. Small code snippets are used where appropriate. You should be able to easily download the files by right-clicking on them and selecting "Save Target As...".

Create a Category.java object to act as an entity for persisting category information about weblog entries. Each category can have many entries. This class contains the following properties:

Configure Spring

Add the 3 new mapping files (that will be generated) to the "sessionFactory" bean's mappingResources property in src/org/appfuse/dao/hibernate/applicationContext-hibernate.xml.


<property name="mappingResources"
    <list> 
        <value>org/appfuse/model/Role.hbm.xml</value>
        <value>org/appfuse/model/User.hbm.xml</value>
        <value>org/appfuse/model/Weblog.hbm.xml</value>
        <value>org/appfuse/model/Entry.hbm.xml</value>
        <value>org/appfuse/model/Category.hbm.xml</value>  
    </list> 
</property> 

[Many-to-Many] A Weblogs can have many Users, a User can have many Weblogs [#2]

A Weblog can have many Users. Basically the idea is of a shared weblog that is a place where many users can express themselves about a particular topic of interest. For this bit of functionality the User object will be modified to have a many-to-many relationship with Weblog.

Add the following users property and accessor methods to Weblog.java.


    private List users = new ArrayList();
    
    /**
     * @hibernate.bag table="weblog_user" cascade="save-update" lazy="true"
     * @hibernate.collection-key column="weblog_id"
     * @hibernate.collection-many-to-many class="org.appfuse.model.User" column="username"
     */
    public List getUsers() {
        return users;
    }

    public void addUser(User user) {
        getUsers().add(user);
    }

    public void setUsers(List users) {
        this.users = users;
    }

NOTE: The reason a java.util.List is used instead of a java.util.Set is because this is because Sets aren't supported for indexed properties in Struts.

User modifications

To allow navigation from a User objec to a list of Weblogs, you need to add a List of Weblogs to the User object. Modify User.java to add a weblogs List and accessor methods.


    protected List weblogs;

    /**
     * @hibernate.bag table="weblog_user" cascade="save-update" lazy="true"
     * @hibernate.collection-key column="username"
     * @hibernate.collection-many-to-many class="org.appfuse.model.Weblog" column="weblog_id"
     */
    public List getWeblogs() {
        return weblogs;
    }

    public void setWeblogs(List weblogs) {
        this.weblogs = weblogs;
    }

Test it!

Create a unit test so you can verify that everything actually works. Create a WeblogDaoTest class in your test/dao/**/dao directory. This file should extend GenericDaoTest. If you're using anything less than AppFuse 1.9, you may have to modify GenericDAOTest's "dao" variable so its protected instead of private. Copy the code below into this test:


package org.appfuse.dao;

import java.util.Date;

import org.appfuse.model.User;
import org.appfuse.model.Weblog;

public class WeblogDaoTest extends GenericDAOTest {
    
    public void testWeblogAndUsers() throws Exception {
        Weblog w = new Weblog();
        w.setBlogTitle("My New Weblog");
        w.setDateCreated(new Date());
        
        // add it to the database
        dao.saveObject(w);
        
        w = (Weblogdao.getObject(Weblog.class, w.getWeblogId());
        
        assertTrue(w.getUsers().isEmpty());
        
        // add a user
        User u = (Userdao.getObject(User.class, "mraible");
        w.addUser(u);
        
        dao.saveObject(w);
        
        w = (Weblogdao.getObject(Weblog.class, w.getWeblogId());
        
        assertTrue(w.getUsers().size() == 1);
        
        // remove the user
        w.setUsers(null);
        dao.saveObject(w);
        
        w = (Weblogdao.getObject(Weblog.class, w.getWeblogId());
        
        assertNull(w.getUsers());
    }
}

Make sure you run ant setup-db before running ant test-dao -Dtestcase=UserDAO. When running the test, you'll probably get a LazyInitializationException. To solve this, change BaseDaoTestCase to extend Spring's AbstractTransactionalDataSourceSpringContextTests if it doesn't already. In additional, you'll need to make a few changes to the existing tests so all the tests pass. Here is a patch. At a minimum, you'll need to fix BaseDAOTestCase and GenericDAOTest (onSetUp() -> onSetupBeforeTransaction()).

Because the WeblogDaoTest is now extending AbstractTransactionalDataSourceSpringContextTests (ATDSSCT), there is a "jdbcTemplate" variable you can use to do additional querying. For instance, you could add the following at the end of your testWeblogAndUsers() method:


        String qry = "select count(*) from weblog_user where username = 'mraible' " +
                     " and weblog_id = " + w.getWeblogId();
        assertEquals(0, jdbcTemplate.queryForInt(qry));

[One-to-Many] A Weblog object can have many Entries [#3]

Now you'll modify the Weblog object and Entry object to represent the multiplicity of a weblog that can have many entries. This relationship is set on the Weblog class using a java.util.List. XDoclet tags are used to establish this relationship using a bag as the Hibernate collection type. Add the following code to your Weblog.java class.


    private List entries;

    /**
     @return Returns the entries.
     
     * @hibernate.bag name="entries" lazy="false" cascade="all"
     * @hibernate.collection-key column="weblog_id"
     * @hibernate.collection-one-to-many class="org.appfuse.model.Entry"
     */
    public List getEntries() {
        return entries;
    }

    public void setEntries(List entries) {
        this.entries = entries;
    }

Modify the Entry class is so it contains a foreign key to its parent Weblog class.


    private Long weblogId;

    /**
     * @hibernate.property column="weblog_id"
     */
    public Long getWeblogId() {
        return weblogId;
    }

    public void setWeblogId(Long weblogId) {
        this.weblogId = weblogId;
    }

At this point, you could add an additional test method to WeblogDaoTest to test that this relationship works. Of course, you'll need to run ant setup-db to make sure your database knows about the relationship.

[Many-to-One] A Category object can be assigned to many entries [#4]

The many-to-one relationship between Category and Entry can be established using XDoclet tags. Hibernate relationships can be established on either side of the relationship or bi-directionally. For this tutorial, this relationship is maintained by a categoryId property and a Category object in Entry. The list of possible categories for a weblog entry will eventually be represented as a drop-down on the UI. Add the following code to your Entry.java class. Notice that the relationship to Category has insert="false" update="false". This is because the object is read-only. This can be useful for displaying a category's name on the UI when you're viewing an Entry.


    private Long categoryId;
    private Category category;

    /**
     * @hibernate.many-to-one insert="false" update="false" cascade="none"
     *  column="category_id" outer-join="true"
     */
    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    /**
     * @hibernate.property column="category_id"
     */
    public Long getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Long categoryId) {
        this.categoryId = categoryId;
    }

After making these changes, your WeblogDaoTest should still pass. Verify it does by running ant setup-db test-dao -Dtestcase=Weblog.

Yeah baby, Yeah!

BUILD SUCCESSFUL
Total time: 24 seconds

Before working on the UI, it's helpful to have some sample data so you don't have to do manual data entry. Add the following XML to the bottom of metadata/sql/sample-data.xml.

  <table name='weblog'>
    <column>weblog_id</column>
    <column>blog_title</column>
    <column>date_created</column>
    <row>
      <value>1</value>
      <value><![CDATA[Sponge Bob is Cool]]></value>
      <value>2004-03-31</value>
    </row>
    <row>
      <value>2</value>
      <value><![CDATA[Java Development = Fun]]></value>
      <value>2005-01-05</value>
    </row>
  </table>
  <table name='weblog_user'>
    <column>weblog_id</column>
    <column>username</column>
    <row>
      <value>1</value>
      <value>tomcat</value>
    </row>
    <row>
      <value>1</value>
      <value>mraible</value>
    </row>
    <row>
      <value>2</value>
      <value>mraible</value>
    </row>
  </table>
  <table name='category'>
    <column>category_id</column>
    <column>name</column>
    <column>description</column>
    <row>
      <value>1</value>
      <value><![CDATA[Struts vs. Spring MVC]]></value>
      <value><![CDATA[Comparing implementations of the MVC Design Pattern]]></value>
    </row>
    <row>
      <value>2</value>
      <value><![CDATA[Cycling Notes]]></value>
      <value><![CDATA[All about cycling in the US.]]></value>
    </row>
    <row>
      <value>3</value>
      <value><![CDATA[Cyclocross]]></value>
      <value><![CDATA[Bog Trotters Unite!]]></value>
    </row>
  </table>
  <table name='entry'>
    <column>entry_id</column>
    <column>entry_text</column>
    <column>time_created</column>
    <column>weblog_id</column>
    <column>category_id</column>
    <row>
      <value>1</value>
      <value><![CDATA[Testing]]></value>
      <value>2005-04-11 01:02:03.4</value>
      <value>1</value>
      <value>1</value>
    </row>
    <row>
      <value>2</value>
      <value><![CDATA[Test Value]]></value>
      <value>2005-04-12 01:02:03.4</value>
      <value>1</value>
      <value>1</value>
    </row>
    <row>
      <value>3</value>
      <value><![CDATA[Test Value 3]]></value>
      <value>2005-04-12 01:02:03.4</value>
      <value>2</value>
      <value>3</value>
    </row>
  </table>

Next Up: Part II: Create Weblog UI - Creating a UI to manage the relationships created in this tutorial.

JSFBeans

Part III: Creating JSF Beans and JSPs - A HowTo for creating JSF Beans and JSPs in an AppFuse project.
This tutorial depends on Part II: Creating new Managers.

About this Tutorial

This tutorial will show you how to create JSF Beans and JSPs. It'll also demonstrate writing a JUnit Test to test PersonForm. The Managed Bean we create will talk to the PersonManager we created in the Creating Managers tutorial. In most web frameworks, the controller logic is contain in an "Action" of some sort. However, with JSF, they're commonly referred to as "Managed Beans". The methods within these beans are called actions. This tutorial is not going to teach you a whole lot about how JSF works, but it will get you up and running quickly with it. If you want a more in-depth learning experience, I suggest you read David Geary's Core JSF. I had it close by my side and used it frequently while integrating JSF into AppFuse. Thanks for the help David and Cay (co-author)!
I will tell you how I do stuff in the Real World in text like this.

Let's get started by creating a new Bean and JSP in AppFuse's architecture. If you haven't installed the JSF module at this point, do so by running ant install-jsf.

Table of Contents

Create personForm.jsp with XDoclet [#1]

In this step, you'll generate a JSP page to display information from the Person object. It will contain JSF's JSP tags that render table rows for each property in Person.java. The AppGen tool that's used to do this is based off a StrutsGen tool - which was originally written by Erik Hatcher. It's basically just a couple of classes and a bunch of XDoclet templates. All these files are located in extras/appgen.

Here are the simple steps to generating the JSP and a properties file containing the labels for the form elements:

# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
Unfortunately, JSF doesn't have an out-of-the-box way to deploy the JSPs to the "WEB-INF/pages" directory at deployment time. The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from the ServletDispatcher. Placing all JSPs below WEB-INF ensures they are only accessed through the Faces Servlet, and not directly by the client or each other. Maybe in a future JSF release.
NOTE: If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file (right after the </content> tag). This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:
body#pageName element.class { background-color: blue } 

Create PersonFormTest to test PersonForm [#2]

To create a JUnit Test for PersonForm, start by creating a PersonFormTest.java file in the test/web/**/action directory.


package org.appfuse.webapp.action;

import org.appfuse.model.Person;

public class PersonFormTest extends BasePageTestCase {
    private PersonForm bean;
    protected void setUp() throws Exception {    
        super.setUp();
        bean = (PersonFormgetManagedBean("personForm");
    }
    protected void tearDown() throws Exception {
        super.tearDown();
        bean = null;
    }

    public void testAdd() throws Exception {
        Person person = new Person();
        // set required fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        bean.setPerson(person);

        assertEquals(bean.save()"list");
        assertFalse(bean.hasErrors());
    }

    public void testEdit() throws Exception {
        log.debug("testing edit...");
        bean.setId("1");

        assertEquals(bean.edit()"edit");
        assertNotNull(bean.getPerson());
        assertFalse(bean.hasErrors());
    }

    public void testSave() {
        bean.setId("1");

        assertEquals(bean.edit()"edit");
        assertNotNull(bean.getPerson());
        Person person = bean.getPerson();

        // update fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        bean.setPerson(person);

        assertEquals(bean.save()"edit");
        assertFalse(bean.hasErrors());
    }

    public void testRemove() throws Exception {
        Person person = new Person();
        person.setId(new Long(2));
        bean.setPerson(person);

        assertEquals(bean.delete()"list");
        assertFalse(bean.hasErrors());
    }
}

Nothing will compile at this point because you need to create the PersonForm that you're referring to in this test.

Create PersonForm [#3]

In src/web/**/action, create a PersonForm.java file with the following contents:


package org.appfuse.webapp.action;

import java.io.Serializable;
import java.util.List;

import org.appfuse.model.Person;
import org.appfuse.service.Manager;

public class PersonForm extends BasePage implements Serializable {
    private PersonManager personManager;
    private Person person = new Person();
    private String id;

    public void setPersonManager(PersonManager manager) {
        this.personManager = manager;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
    public void setId(String id) {
        this.id = id;
    }

    public String delete() {
        personManager.removePerson(String.valueOf(person.getId()));
        addMessage("person.deleted");

        return "list";
    }

    public String edit() {
        if (id != null) {
            person = personManager.getPerson(id);
        else {
            person = new Person();
        }

        return "edit";
    }

    public String save() {

        boolean isNew = (person.getId() == null);

        personManager.savePerson(person);

        String key = (isNew"person.added" "person.updated";
        addMessage(key);

        if (isNew) {
            return "list";
        else {
            return "edit";
        }
    }
}

You'll notice a number of keys in this file - "person.deleted", "person.added" and "person.updated". These are all keys that need to be in your i18n bundle (ApplicationResources.properties). You should've added these at the beginning of this tutorial. If you want to customize these messages, to add the a person's name or something, simply add a {0} placeholder in the key's message and then use the addMessage(key, stringtoreplace) method. You can also use an Object for the stringtoreplace variable if you want to make multiple substitutions.

You might notice that the code we're using to call the PersonManager is the same as the code we used in our PersonManagerTest. Both PersonForm and PersonManagerTest are clients of PersonManagerImpl, so this makes perfect sense.

Now you need to tell JSF that this bean exists. First, add a managed-bean definition for PersonForm to web/WEB-INF/faces-config.xml:


    <managed-bean>
        <managed-bean-name>personForm</managed-bean-name>
        <managed-bean-class>org.appfuse.webapp.action.PersonForm</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>id</property-name>
            <value>#{param.id}</value>
        </managed-property>
        <managed-property>
            <property-name>personManager</property-name>
            <value>#{personManager}</value>
        </managed-property>
    </managed-bean>

Spring's DelegatingVariableResolver helps JSF to resolve "#{personManager}" to the "personManager" Spring bean. This "variableResolver" has already been declared at the top of the faces-config.xml file.

Then add navigation-rules that control where it goes after actions are executed:


    <navigation-rule>
        <from-view-id>/personForm.jsp</from-view-id>
        <navigation-case>
            <from-outcome>cancel</from-outcome>
            <to-view-id>/mainMenu.jsp</to-view-id>
            <redirect/>
        </navigation-case>
        <navigation-case>
            <from-outcome>list</from-outcome>
            <to-view-id>/mainMenu.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    </navigation-rule>

The PersonForm returns "list" from the cancel(), delete() and save() methods. In the next tutorial, you will change the "list" in the navigation-rule to point to the list screen.

Run the PersonFormTest [#4]

If you look at our PersonFormTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so add those records to your sample data file (metadata/sql/sample-data.xml). I'd just add it at the bottom - order is not important since it (currently) does not relate to any other tables.
  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

DBUnit loads this file before we running any of the tests, so this record will be available to your Form test.

Make sure are in the base directory of your project and all files are saved. If you run ant test-web -Dtestcase=PersonForm - everything should work as planned.

BUILD SUCCESSFUL
Total time: 9 seconds

Clean up the JSP to make it presentable [#5]

If you want to add a usability enhancement to your form, you can set the cursor to focus on the first field when the page loads. Simply add the following JavaScript at the bottom of your form (web/personForm.jsp):
<script type="text/javascript">
    document.forms["person"].elements["firstName"].focus();
</script>

Now if you execute ant db-load deploy, start Tomcat and point your browser to http://localhost:8080/appfuse/personForm.html, you should see something like this:

personForm-final.png

With JSF, everything is a post, so if you want to edit a user, you cannot get to them from a URL. To edit a person, add the following to the mainMenu.jsp:

    <h:commandLink value="Edit Person" action="#{personForm.edit}">
        <f:param name="id" value="1"/>
    </h:commandLink>

Then add a <navigation-rule> near the top of web/WEB-INF/faces-config.xml:

    <navigation-rule>
        <from-view-id>/mainMenu.jsp</from-view-id>
        <navigation-case>
            <from-outcome>edit</from-outcome>
            <to-view-id>/personForm.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>

Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, but this can easily be done by adding text (using <fmt:message>) at the top of the personForm.jsp page.

[Optional] Create a Canoo WebTest to test browser-like actions [#6]

The final (optional) step in this tutorial is to create a Canoo WebTest to test the JSPs.
I say this step is optional, because you can run the same tests through your browser.

You can use the following steps to test the different actions for adding, editing and saving a user.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.

I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing -Dtestcase=Name, I've found that I'm used to doing CamelCase for my JUnit Tests.


    <!-- runs person-related tests -->
    <target name="PersonTests"
        depends="EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

    <!-- Verify the edit person screen displays without errors -->
    <target name="EditPerson"
        description="Tests editing an existing Person's information">
        <webtest name="editPerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <verifytitle description="we should see the personDetail title"
                    text=".*${personDetail.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

    <!-- Edit a person and then save -->
    <target name="SavePerson"
        description="Tests editing and saving a user">
        <webtest name="savePerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <verifytitle description="we should see the personDetail title"
                    text=".*${personDetail.title}.*" regex="true"/>
                <!-- update some of the required fields -->
                <setinputfield description="set firstName" name="personForm:firstName" value="Canoo"/>
                <setinputfield description="set lastName" name="personForm:lastName" value="WebTest"/>
                <clickbutton label="${button.save}" description="Click Save"/>
                <verifytitle description="Page re-appears if save successful"
                    text=".*${personDetail.title}.*" regex="true"/>
                <verifytext description="verify success message" text="${person.updated}"/>
            </steps>
        </webtest>
    </target>

    <!-- Add a new Person -->
    <target name="AddPerson"
        description="Adds a new Person">
        <webtest name="addPerson">
            &config;
            <steps>
                &login;
                <invoke description="View Person Form" url="/personForm.html"/>
                <verifytitle description="we should see the personDetail title"
                    text=".*${personDetail.title}.*" regex="true"/>
                <!-- enter required fields -->
                <setinputfield description="set firstName" name="personForm:firstName" value="Jack"/>
                <setinputfield description="set lastName" name="personForm:lastName" value="Raible"/>
                <clickbutton label="${button.save}" description="Click button 'Save'"/>
                <verifytitle description="Main Menu appears if save successful" 
                    text=".*${mainMenu.title}.*" regex="true"/>
                <verifytext description="verify success message" text="${person.added}"/>
            </steps>
        </webtest>
    </target>

    <!-- Delete existing person -->
    <target name="DeletePerson"
        description="Deletes existing Person">
        <webtest name="deletePerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/> 
                <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
                <verifyNoDialogResponses/>
                <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
                <verifytext description="verify success message" text="${person.deleted}"/>
            </steps>
        </webtest>
    </target>

After adding this, you should be able to run ant test-canoo -Dtestcase=PersonTests with Tomcat running or ant test-jsp -Dtestcase=PersonTests if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.

You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add tweak the log4j settings in web/WEB-INF/classes/log4j.properties.

BUILD SUCCESSFUL
Total time: 11 seconds


Next Up: Part IV: Adding Validation and List Screen - Explains the validation logic on the personForm and how the firstName and lastName are required fields. You'll also add a list screen to display all person records in the database.

RunningOnOracle

The following instructions describe the steps needed to configure AppFuse so that the applications it generates will work with an Oracle database.

Table of Contents

Create Database Schema [#1]

Create the Oracle Database schema account that will hold your application's tables. This value should correspond to the -Ddb.name you use when running the ant new task.

CREATE USER <database-schema-owner> IDENTIFIED BY <password>;  
GRANT CONNECT TO <database-schema-owner>;
GRANT RESOURCE TO <database-schema-owner>;

Setup JDBC Driver [#2]

Obtain the Oracle JDBC driver appropriate for your database and JDK version. (Oracle's license agreement will be shown just be downloading, make sure you read and understand it.)

NOTE: According to the Oracle JDBC Driver README , classes12.jar is for JDK1.2 & 1.3, ojdbc14.jar is for JDK 1.4. The instructions here assume you will be using ojdbc14.jar, modify the instructions below accordingly if otherwise.

Next, in your {AppFuse home}/lib directory, add an "oracle" directory and place the ojdbc14.jar in it.

Edit build.properties in the main AppFuse Directory [#3]

Next, the build.properties file needs to be updated to use an Oracle (instead of the default MySQL) database.

Here is a idea of what the database entries should look like for Oracle (modify the various fields as appropriate for your system):

database.jar=${lib.dir}/oracle/ojdbc14.jar
database.type=oracle
database.host=localhost
#use the database schema owner and password created in step #1 above here
database.username=username
database.password=password
database.schema=schemaname (case sensitive using 10g)

hibernate.dialect=org.hibernate.dialect.Oracle9Dialect
database.driver_class=oracle.jdbc.driver.OracleDriver
database.url=jdbc:oracle:thin:@localhost:1521:mySID

You'll also need to add the "database.schema" property to build.xml - in the <dbunit> tasks. For example:

<dbunit driver="${database.driver_class}" schema="${database.schema}"
    supportBatchStatement="false" url="${database.url}"
    userid="${database.username}" password="${database.password}">

WARNING: Avoid the use of "appfuse" as values for any of the parameters here. They may end up getting modified when you run the "ant new" task below.

NOTE: In the listings above, database.host should refer to the machine the Oracle database is running on. Also, the "mySID" at the end of the database.url value should refer to the SERVICE_NAME within the tnsnames.ora database file on the database server machine.

NOTE: There is also a database.name parameter available, but you should only add it to build.properties if you want to use a different database name than provided in the -Ddb.name parameter when you ran the ant new task.

Generate Application [#4]

Run ant new -Dapp.name=myappname -Ddb.name=mydbname as usual to generate the new web application directory.

For mydbname, use the schema owner name you created in Step #1 above.

Deploy Application [#5]

Run "ant setup" within your new web app directory to compile and deploy your new application on Tomcat. Next, Start Tomcat and bring up the application: http://localhost:8080/myappname

NOTE: The Oracle database server may be configured to occupy port 8080 with its own servlet container, which you may find very difficult to disable. This can cause port conflicts if you are running Tomcat on the same machine. If this problem occurs, it may be easiest to shut down the Oracle database, start Tomcat (so it will occupy port 8080), and then restart Oracle.

Test Database Access [#6]

To test that database accesses are working properly, edit one of the profiles within the sample application and change the country listed. Then, within Oracle SQL*Plus, log in as the schema owner and issue this command:

select username, country from app_user;  

If everything is working correctly, you should see that the country value has changed for the profile that you edited.

SpringControllers

Part III: Creating Controllers and JSPs - A HowTo for creating Spring Controllers and JSPs in an AppFuse project.
This tutorial depends on Part II: Creating new Managers.

About this Tutorial

This tutorial will show you how to create Spring Controllers and JSPs. It'll also demonstrate writing JUnit Tests to test your Controllers. The Controller we create will talk to the PersonManager we created in the Creating Managers tutorial.
I will tell you how I do stuff in the Real World in text like this.

Let's get started by creating a new Controller and JSP in AppFuse's architecture. If you haven't installed the Spring MVC module at this point, do so by running ant install-springmvc.

Table of Contents

Create a skeleton JSP using XDoclet [#1]

In this step, you'll generate a JSP page to display information from the Person object. It will contain Spring's JSP tags that render table rows for each property in Person.java. The AppGen tool that's used to do this is based off a StrutsGen tool - which was originally written by Erik Hatcher. It's basically just a couple of classes and a bunch of XDoclet templates. All these files are located in extras/appgen.

Here are the simple steps to generating the JSP and a properties file containing the labels for the form elements:

# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
The files in the "pages" directory will end up in "WEB-INF/pages" at deployment time. The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from the DispatchServlet. Placing all JSPs below WEB-INF ensures they are only accessed through Controllers, and not directly by the client or each other. This allows security to be moved up into the Controller, where it can be handled more efficiently, and out of the base presentation layer.

The web application security for AppFuse specifies that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through an Action to get to a JSP (or at least the ones in pages).

NOTE: If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file. This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:
body#pageName element.class { background-color: blue } 
In the generated JSPs, there are two keys for the title (top of the browser window) and the header (heading in the page). We now need to add these two keys (personDetail.title and personDetail.heading) to ApplicationResources.properties.
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
Just above, we added "personForm.*" keys to this file, so why do I use personForm and personDetail? The best reason is because it gives a nice separation between form labels and text on the page. Another reason is because all the *Form.* give you a nice representation of all the fields in your database.

I recently had a client who wanted all fields in the database searchable. This was fairly easy to do. I just looked up all the keys in ApplicationResources.properties which contained "Form." and then put them into a drop-down. On the UI, the user was able to enter a search term and select the column they wanted to search. I was glad I followed this Form vs. Detail distinction on that project!

Create PersonFormControllerTest to test PersonFormController [#2]

To create a JUnit Test for the PersonFormController, start by creating a PersonFormControllerTest.java file in the test/web/**/action directory.


package org.appfuse.webapp.action;

import org.appfuse.model.Person;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;

public class PersonFormControllerTest extends BaseControllerTestCase {
    private PersonFormController c;
    private MockHttpServletRequest request;
    private ModelAndView mv;

    protected void setUp() throws Exception {
        // needed to initialize a user
        super.setUp();
        c = (PersonFormControllerctx.getBean("personFormController");
    }

    protected void tearDown() {
        c = null;
    }

    public void testEdit() throws Exception {
        log.debug("testing edit...");
        request = newGet("/editPerson.html");
        request.addParameter("username""tomcat");

        mv = c.handleRequest(request, new MockHttpServletResponse());

        assertEquals("personForm", mv.getViewName());
    }

    public void testSave() throws Exception {
        request = newGet("/editPerson.html");
        request.addParameter("id""1");

        mv = c.handleRequest(request, new MockHttpServletResponse());

        Person person = (Personmv.getModel().get(c.getCommandName());
        assertNotNull(person);
        
        request = newPost("/editPerson.html");
        super.objectToRequestParameters(person, request);
        request.addParameter("lastName""Updated Last Name");
        
        mv = c.handleRequest(request, new MockHttpServletResponse());
        
        Errors errors =
            (Errorsmv.getModel().get(BindException.ERROR_KEY_PREFIX + "person");
        assertNull(errors);
        assertNotNull(request.getSession().getAttribute("messages"));        
    }
    
    public void testRemove() throws Exception {
        request = newPost("/editPerson.html");
        request.addParameter("delete""");
        request.addParameter("id""2");
        mv = c.handleRequest(request, new MockHttpServletResponse());
        assertNotNull(request.getSession().getAttribute("messages"));
    }
}

Nothing will compile at this point (ant compile) because you need to create the PersonFormController that you're referring to in this test.

Create PersonFormController [#3]

In src/web/**/action, create a PersonFormController.java file with the following contents:


package org.appfuse.webapp.action;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

public class PersonFormController extends BaseFormController {
    private PersonManager mgr = null;

    public void setPersonManager(PersonManager mgr) {
        this.mgr = mgr;
    }

    protected Object formBackingObject(HttpServletRequest request)
    throws Exception {
        String id = request.getParameter("id");
        Person person = null;

        if (!StringUtils.isEmpty(id)) {
            person = mgr.getPerson(id);
        else {
            person = new Person();
        }


        return person;
    }

    public ModelAndView processFormSubmission(HttpServletRequest request,
                                              HttpServletResponse response,
                                              Object command,
                                              BindException errors)
    throws Exception {
        if (request.getParameter("cancel"!= null) {
            return new ModelAndView(new RedirectView(getSuccessView()));
        }

        return super.processFormSubmission(request, response, command, errors);
    }

    public ModelAndView onSubmit(HttpServletRequest request,
                                 HttpServletResponse response, Object command,
                                 BindException errors)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("entering 'onSubmit' method...");
        }

        Person person = (Personcommand;
        boolean isNew = (person.getId() == null);
        String success = getSuccessView();
        Locale locale = request.getLocale();

        if (request.getParameter("delete"!= null) {
            mgr.removePerson(person.getId().toString());

            saveMessage(request, getText("person.deleted", locale));
        else {
            mgr.savePerson(person);

            String key = (isNew"person.added" "person.updated";
            saveMessage(request, getText(key, locale));

            if (!isNew) {
                success = "editPerson.html?id=" + person.getId();
            }
        }

        return new ModelAndView(new RedirectView(success));
    }
}

In the class above, there are a few methods you might not be familiar with. The formBackingObject() method is used to supply the object this Controller operates on. The processFormSubmission() method is used to detect the cancel button, and onSubmit() is called on POST requests and handles delete/add/update of a user.

There are a few keys you (might) need to add to ApplicationResources.properties to display the success messages. This file is located in web/WEB-INF/classes - open it and add the following:

I usually add these under the # -- success messages -- comment.
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

You could use generic added, deleted and updated messages, whatever works for you. It's nice to have separate messages in case these need to change on a per-entity basis.

You might notice that the code we're using to call the PersonManager is the same as the code we used in our PersonManagerTest. Both PersonFormController and PersonManagerTest are clients of PersonManagerImpl, so this makes perfect sense.

Now you need to add a url-mapping for this controller in the web/WEB-INF/action-servlet.xml file. In the block below, the new line is at the bottom, with <prop key="/editPerson.html">:


    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/editProfile.html">userFormController</prop>
                <prop key="/mainMenu.html">filenameController</prop>
                <prop key="/editUser.html">userFormController</prop> 
                <prop key="/selectFile.html">filenameController</prop>
                <prop key="/uploadFile.html">fileUploadController</prop>
                <prop key="/passwordHint.xml">passwordHintController</prop>
                <prop key="/signup.xml">signupController</prop>
                <prop key="/editPerson.html">personFormController</prop>
            </props>
        </property>
    </bean>

You also need to add the <bean> definition for personFormController in this same file:


    <bean id="personFormController" class="org.appfuse.webapp.action.PersonFormController">
        <property name="commandName"><value>person</value></property>
        <property name="commandClass"><value>org.appfuse.model.Person</value></property>
        <!--property name="validator"><ref bean="beanValidator"/></property-->
        <property name="formView"><value>personForm</value></property>
        <property name="successView"><value>mainMenu.html</value></property>
        <property name="personManager"><ref bean="personManager"/></property>
    </bean>

The "validator" property is commented out in the above XML block because we haven't defined any validation rules for the Person object. We'll uncomment this value when we add validation.

Run the PersonFormControllerTest [#4]

If you look at our PersonFormControllerTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so let's add those records to our sample data file (metadata/sql/sample-data.xml). I'd just add it at the bottom - order is not important since it (currently) does not relate to any other tables.
  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

DBUnit loads this file before we running any of the tests, so this record will be available to your Controller test.

Make sure are in the base directory of your project. If you run ant test-web -Dtestcase=PersonFormController - everything should work as planned.

BUILD SUCCESSFUL
Total time: 21 seconds

Clean up the JSP to make it presentable [#5]

If you want to add a usability enhancement to your form, you can set the cursor to focus on the first field when the page loads. Simply add the following JavaScript at the bottom of your form:

<script type="text/javascript">
    document.forms["person"].elements["firstName"].focus();
</script>

Now if you execute ant db-load deploy, start Tomcat and point your browser to http://localhost:8080/appfuse/editPerson.html?id=1, you should see something like this:

CreateActions/personForm-final.png

Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, but this can easily be done by adding text (using <fmt:message>) at the top of the personForm.jsp page.

[Optional] Create a Canoo WebTest to test browser-like actions [#6]

The final (optional) step in this tutorial is to create a Canoo WebTest to test the JSPs.
I say this step is optional, because you can run the same tests through your browser.

You can use the following URLs to test the different actions for adding, editing and saving a user.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.

I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing -Dtestcase=Name, I've found that I'm used to doing CamelCase for my JUnit Tests.


<!-- runs person-related tests -->
<target name="PersonTests"
    depends="EditPerson,SavePerson,AddPerson,DeletePerson"
    description="Call and executes all person test cases (targets)">
    <echo>Successfully ran all Person JSP tests!</echo>
</target>

<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson"
    description="Tests editing an existing Person's information">
    <webtest name="editPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
        </steps>
    </webtest>
</target>

<!-- Edit a person and then save -->
<target name="SavePerson"
    description="Tests editing and saving a user">
    <webtest name="savePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set lastName" name="lastName" value="Canoo"/>
            <clickbutton label="Save" description="Click Save"/>
            <verifytitle description="Page re-appears if save successful"
                text=".*${personDetail.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.updated}"/>
        </steps>
    </webtest>
</target>

<!-- Add a new Person -->
<target name="AddPerson"
    description="Adds a new Person">
    <webtest name="addPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Add Button" url="/editPerson.html"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set firstName" name="firstName" value="Abbie"/>
            <setinputfield description="set lastName" name="lastName" value="Raible"/>
            <clickbutton label="${button.save}" description="Click button 'Save'"/>
            <verifytitle description="Main Menu appears if save successful"
                text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.added}"/>
        </steps>
    </webtest>
</target>

<!-- Delete existing person -->
<target name="DeletePerson"
    description="Deletes existing Person">
    <webtest name="deletePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
            <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
            <verifyNoDialogResponses/>
            <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.deleted}"/>
        </steps>
    </webtest>
</target>

After adding this, you should be able to run ant test-canoo -Dtestcase=PersonTests with Tomcat running or ant test-jsp -Dtestcase=PersonTests if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.

You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add the following between </webtest> and </target> at the end of each target.

<loadfile property="web-tests.result" 
    srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>
BUILD SUCCESSFUL
Total time: 10 seconds


Next Up: Part IV: Adding Validation and List Screen - Adding validation logic to the personForm so that firstName and lastName are required fields and adding a list screen to display all person records in the database.

TapestryPages

Part III: Creating Tapestry Pages and HTML Templates - A HowTo for creating Tapestry Pages and HTML Templates in an AppFuse project.
This tutorial depends on Part II: Creating new Managers.

About this Tutorial

This tutorial will show you how to create Tapestry Pages and HTML Templates. It'll also demonstrate writing a JUnit Test to test PersonForm. The Page class we create will talk to the PersonManager we created in the Creating Managers tutorial. In most web frameworks, the controller logic is contain in an "Action" of some sort. However, with Tapestry, they're commonly referred to as "Page". The methods withing these pages are called listeners. This tutorial is not going to teach you a whole lot about how Tapestry works, but it will get you up and running quickly with it. If you want a more in-depth learning experience, I suggest you read Howard Lewis Ship's Tapestry in Action. I had it close by my side and used it frequently while integrating Tapestry into AppFuse. Thanks for the help Howard!
I will tell you how I do stuff in the Real World in text like this.

Let's get started by creating a new Page and HTML Template in AppFuse's architecture. If you haven't installed the Tapestry module at this point, do so by running ant install-tapestry.

Table of Contents

Create pageForm.html Template using XDoclet [#1]

In this step, you'll generate a an HTML Template to display information from the Person object. It will contain Tapestry's syntax for rendering form elements - which is just HTML with "jwcid" attributes. The AppGen tool that's used to do this is based off a StrutsGen tool - which was originally written by Erik Hatcher. It's basically just a couple of classes and a bunch of XDoclet templates. All these files are located in extras/appgen.

Here are the simple steps to generating the HTML Template and a properties file containing the labels for the form elements:

# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
The files in the "pages" directory will end up in "WEB-INF/pages" at deployment time. The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from Tapestry's ApplicationServlet. Placing all HTML templates below WEB-INF ensures they are only accessed through Pages, and not directly by the client or each other. This allows security to be moved up into the Page, where it can be handled more efficiently, and out of the base presentation layer.

The web application security for AppFuse specifies that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through a Page to get to a template (or at least the ones in pages).

NOTE: If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file (right after the </content> tag). This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:
body#pageName element.class { background-color: blue } 

Create PersonFormTest to test PersonForm [#2]

To create a JUnit Test for PersonForm, start by creating a PersonFormTest.java file in the test/web/**/action directory.


package org.appfuse.webapp.action;

import java.util.ResourceBundle;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;

public class PersonFormTest extends BasePageTestCase {
    private PersonForm page;
    private PersonManager personManager;

    protected void setUp() throws Exception {    
        super.setUp();
        page = (PersonFormgetPage(PersonForm.class);
        // unfortunately this is a required step if you're calling 
        // getMessage in the page class
        page.setBundle(ResourceBundle.getBundle(MESSAGES));
        page.setValidationDelegate(new Validator());

        // this manager can be mocked if you want a more "pure" unit test
        personManager = (PersonManagerctx.getBean("personManager");
        page.setPersonManager(personManager);
        // default request cycle
        page.setRequestCycle(getCycle(request, response));
    }

    protected void tearDown() throws Exception {
        super.tearDown();
        page = null;
    }

    public void testAdd() throws Exception {
        Person person = new Person();
        // set required fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        page.setPerson(person);

        page.save(page.getRequestCycle());
        assertFalse(page.hasErrors());
    }

    public void testEdit() throws Exception {
        MockRequestCycle cycle = (MockRequestCyclepage.getRequestCycle();
        cycle.addServiceParameter(new Long(1));
        
        page.edit(cycle);

        assertNotNull(page.getPerson());
        assertFalse(page.hasErrors());
    }
    
    public void testSave() {
        assertNotNull(personManager);
        Person person = personManager.getPerson("1");

        // update fields
        person.setFirstName("firstName");
        person.setLastName("lastName");
        page.setPerson(person);

        page.save(page.getRequestCycle());
        assertFalse(page.hasErrors());
    }

    public void testRemove() throws Exception {
        Person person = new Person();
        person.setId(new Long(2));
        page.setPerson(person);

        page.delete(page.getRequestCycle());
        assertFalse(page.hasErrors());
    }
}

Nothing will compile at this point because you need to create the PersonForm that you're referring to in this test.

Create PersonForm [#3]

In src/web/**/action, create a PersonForm.java file with the following contents:


package org.appfuse.webapp.action;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.engine.ILink;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageBeginRenderListener;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;

public abstract class PersonForm extends BasePage implements PageBeginRenderListener {
    public abstract PersonManager getPersonManager();
    public abstract void setPerson(Person person);
    public abstract Person getPerson();

    public void pageBeginRender(PageEvent event) {
        if ((getPerson() == null&& !event.getRequestCycle().isRewinding()) {
            setPerson(new Person());
        else if (event.getRequestCycle().isRewinding()) { // add
            setPerson(new Person());
        }
    }

    public ILink cancel(IRequestCycle cycle) {
        log.debug("Entering 'cancel' method");
        return getEngineService().getLink(false, "persons");
    }

    public ILink delete(IRequestCycle cycle) {
        log.debug("entered 'delete' method");

        getPersonManager().removePerson(getPerson().getPersonId().toString());

        PersonList nextPage = (PersonListcycle.getPage("persons");
        nextPage.setMessage(getText("person.deleted"));
        return getEngineService().getLink(false, nextPage.getPageName());
    }

    public ILink save(IRequestCycle cycle) {
        if (getDelegate().getHasErrors()) {
            return null;
        }

        boolean isNew = (getPerson().getPersonId() == null);

        getPersonManager().savePerson(getPerson());

        String key = (isNew"person.added" "person.updated";

        if (isNew) {
            PersonList nextPage = (PersonListcycle.getPage("persons");
            nextPage.setMessage(getText(key));
            return getEngineService().getLink(false, nextPage.getPageName());
        else {
            setMessage(getText(key));
            return null// return to current page
        }
    }
}

You'll notice a number of keys in this file - "person.deleted", "person.added" and "person.updated". These are all keys that need to be in your i18n bundle (ApplicationResources.properties). You should've added these at the beginning of this tutorial. If you want to customize these messages, to add the a person's name or something, simply add a {0} placeholder in the key's message and then use setMessage(format(key, stringtoreplace)) method.

You might notice that the code we're using to call the PersonManager is the same as the code we used in our PersonManagerTest. Both PersonForm and PersonManagerTest are clients of PersonManagerImpl, so this makes perfect sense.

Now you need to tell Tapestry that this page exists. To do this, add a page entry in the web/WEB-INF/tapestry.application file.


    <page name="personForm" specification-path="pages/personForm.page"/>

If you keep your HTML templates in the WEB-INF directory, the above step is unnecessary. Hopefully a future version of Tapestry will allow you to set a global specification-path.

The PersonForm returns the "MainMenu" page from the cancel()(, delete() and save() methods. In the next tutorial, you will change this to be the PersonList.

Run the PersonFormTest [#4]

If you look at our PersonFormTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so add those records to your sample data file (metadata/sql/sample-data.xml). I'd just add it at the bottom - order is not important since it (currently) does not relate to any other tables.
  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

DBUnit loads this file before we running any of the tests, so this record will be available to your Form test.

Make sure are in the base directory of your project and all files are saved. If you run ant test-web -Dtestcase=PersonForm - everything should work as planned.

BUILD SUCCESSFUL
Total time: 12 seconds

View the form in your browser [#5]

Now if you execute
ant db-load deploy, start Tomcat and point your browser to http://localhost:8080/appfuse/personForm.html, you should see something like this:
JSFBeans/personForm-final.png
NOTE: Tapestry will automatically put focus on the first required field of the form. If you want to change this, see the mailing list archives.

With Tapestry, the URLs are kinda ugly, but they hold a lot of information in them. Unlike other frameworks, where you typically call methods in an Action, with Tapestry - you call listeners in a Page class. To call the "edit" listener in the PersonForm class, add the following to web/pages/mainMenu.html.

    <a jwcid="@DirectLink" listener="ognl:requestCycle.getPage('personForm').listeners.edit" 
        parameters="ognl:new java.lang.Long(1)">Edit Person</a>

Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, but this can easily be done by adding text (using <span key="..."/>) at the top of the personForm.html page.

[Optional] Create a Canoo WebTest to test browser-like actions [#6]

The final (optional) step in this tutorial is to create a Canoo WebTest to test the HTML Templates.
I say this step is optional, because you can run the same tests through your browser.

You can use the following steps to test the different actions for adding, editing and saving a user.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.

I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing -Dtestcase=Name, I've found that I'm used to doing CamelCase for my JUnit Tests.


    <!-- runs person-related tests -->
    <target name="PersonTests"
        depends="EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person HTML Template tests!</echo>
    </target>

    <!-- Verify the edit person screen displays without errors -->
    <target name="EditPerson"
        description="Tests editing an existing Person's information">
        <webtest name="editPerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <verifytitle description="we should see the personDetail title"
                    text=".*${personDetail.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

    <!-- Edit a person and then save -->
    <target name="SavePerson"
        description="Tests editing and saving a user">
        <webtest name="savePerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <verifytitle description="we should see the personDetail title"
                    text=".*${personDetail.title}.*" regex="true"/>
                <!-- update some of the required fields -->
                <setinputfield description="set firstName" name="firstNameField" value="Canoo"/>
                <setinputfield description="set lastName" name="lastNameField" value="WebTest"/>
                <clickbutton label="${button.save}" description="Click Save"/>
                <verifytitle description="Page re-appears if save successful"
                    text=".*${personDetail.title}.*" regex="true"/>
                <verifytext description="verify success message" text="${person.updated}"/>
            </steps>
        </webtest>
    </target>

    <!-- Add a new Person -->
    <target name="AddPerson"
        description="Adds a new Person">
        <webtest name="addPerson">
            &config;
            <steps>
                &login;
                <invoke description="View Person Form" url="/personForm.html"/>
                <verifytitle description="we should see the personDetail title"
                    text=".*${personDetail.title}.*" regex="true"/>
                <!-- enter required fields -->
                <setinputfield description="set firstName" name="firstNameField" value="Jack"/>
                <setinputfield description="set lastName" name="lastNameField" value="Raible"/>
                <clickbutton label="${button.save}" description="Click button 'Save'"/>
                <verifytitle description="Main Menu appears if save successful" 
                    text=".*${mainMenu.title}.*" regex="true"/>
                <verifytext description="verify success message" text="${person.added}"/>
            </steps>
        </webtest>
    </target>

    <!-- Delete existing person -->
    <target name="DeletePerson"
        description="Deletes existing Person">
        <webtest name="deletePerson">
            &config;
            <steps>
                &login;
                <clicklink label="Edit Person"/>
                <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
                <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
                <verifyNoDialogResponses/>
                <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
                <verifytext description="verify success message" text="${person.deleted}"/>
            </steps>
        </webtest>
    </target>

After adding this, you should be able to run ant test-canoo -Dtestcase=PersonTests with Tomcat running or ant test-html -Dtestcase=PersonTests if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.

You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add tweak the log4j settings in web/WEB-INF/classes/log4j.properties.

BUILD SUCCESSFUL
Total time: 27 seconds


Next Up: Part IV: Adding Validation and List Screen - Explains the validation logic on the personForm and how the firstName and lastName are required fields. You'll also add a list screen to display all person records in the database.

ValidationAndList

Part IV: Adding Validation and List Screen - Adding validation logic to the PersonForm so that firstName and lastName are required fields and adding a list screen to display all person records in the database.
This tutorial depends on Part III: Creating Actions and JSPs.

About this Tutorial

This tutorial will show you how to add Validation logic (client and server-side) to the PersonForm object using Struts' Validator. We'll also create a list screen using the Display Tag Library to display all the people in the database.
I will tell you how I do stuff in the Real World in text like this.

Table of Contents

Add XDoclet Validator tags to Person.java [#1]

To use the Struts Validator, normally you have to write a validation.xml file by hand. If you're not using AppFuse, you also have to configure the Validator Plugin and error keys in your ApplicationResources.properties. For more information on this, see the Validation Made Easy Tutorial (there's also a rich set of tutorials for Struts itself).

Thanks to XDoclet, it's much easier - you just need to add a couple of @struts.validator tags to the Person class. Open it up (src/dao/**/model/Person.java) and modify the getFirstName() and getLastName() methods to include @struts.validator type="required" tags.


    /**
     * @struts.validator type="required"
     * @hibernate.property column="first_name" length="50"
     */
    public String getFirstName() {
        return this.firstName;
    }

    /**
     * @struts.validator type="required" 
     * @hibernate.property column="last_name" length="50"
     */
    public String getLastName() {
        return this.lastName;
    }

You can also add a msgkey attribute to this tag to override the default message key for this error.


@struts.validator type="required" msgkey="errors.required"

The default key for type="required" is already errors.required, so I usually leave it to the default. This key is defined in web/WEB-INF/classes/ApplicationResources_*.properties. You'll notice that we put these tags on the getters of this class even though the XDoclet documentation says to put them on the setters. This is because we are generating our PersonForm.java - the template file (metadata/template/struts_form.xdt) takes care of putting these tags onto the setters in the generated file.

Now if you save Person.java and run ant clean webdoclet, a validation.xml file will be generated in build/appfuse/WEB-INF/. Its contents should have now have an entry for "personForm".


      <form name="personForm">
              <field property="firstName"
                     depends="required">

                  <arg0 key="personForm.firstName"/>
              </field>
              <field property="lastName"
                     depends="required">

                  <arg0 key="personForm.lastName"/>
              </field>
      </form>

Client-side validation is enabled by default in personForm.jsp. There is an <html:javascript> JSP tag and script at the bottom of this page that enables it. The following should already exist (thanks to AppGen) - but you might need to uncomment it if you commented it out in the last tutorial.

<html:javascript formName="personForm" cdata="false"
    dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
    src="<html:rewrite page="/scripts/validator.jsp"/>"></script>
NOTE: If you have nested objects with validation rules, those will be picked up and put into validation.xml. This is because an @struts.validator tag gets added to the setter of the nested object when the form is generated (using metadata/templates/struts_form.xdt). If you have many-to-many bi-directional relationships between objects, this can cause a problem. There are two solutions to fix this. The first is to remove the @struts.validator tag from struts_form.xdt and manually place it on the setter in your POJO. The second is described here.

View JSP with validation added and test [#2]

Now that you have Validation configured for this form, whenever this form is used in an action-mapping with validate="true", these rules will be applied. In the last tutorial, we added the "savePerson" action-mapping for PersonAction. The XDoclet tags for this action-mapping were:


 * @struts.action name="personForm" path="/savePerson" scope="request"
 *  validate="true" parameter="method" input="edit"

So now, as long as your web/pages/personForm.jsp has <html:form action="savePerson">, validation should kick in when we try to save this form. Run ant db-load deploy, start Tomcat and go to http://localhost:8080/appfuse/editPerson.html?id=1.

If you erase the values in the firstName and lastName fields and click the save button, you should get the following JavaScript alert.

validation-required.png

To make sure things are really working as expected, you can turn off JavaScript and ensure the server-side validation is working. This is easy in Firefox (my favorite browser), just go to Tools → Options → Web Features and uncheck "Enable JavaScript". Now if you clear the fields and save the form, you should see the following:

validation-required-nojs.png

If you don't see these validation errors, there are a couple possibilities:

This is because the <html:form> in web/pages/personForm.jsp has action="editPerson" - make sure it has action="savePerson".
The blank page indicates that the "input" attribute of you "savePerson" forward is incorrectly configured. Make sure it relates to a local or global action-forward. In this example, it should be input="edit", which points to the .personDetail tile's definition. From my experience, the input's value must be a forward, not a path to an action.
If you only want server-side validation (no JavaScript), you can remove the onsubmit attribute of <html:form> (in web/pages/personForm.jsp) as well as the Validator JavaScript tags at the bottom of the page.


<html:javascript formName="personForm" cdata="false"
      dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
      src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

Add testGetPeople methods to DAO and Manager Tests [#3]

To create a List screen (also called a master screen), we need to create methods that will return all the rows from our person table. Let's start by adding tests for these methods to our PersonDaoTest and PersonManagerTest classes. I usually name this method getEntities (i.e. getUsers), but you could also use getAll or search - it's really just a matter of personal preference.

Open test/dao/**/dao/PersonDaoTest.java and add a testGetPeople method:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

The reason I'm passing in a person object to the getPeople method is to allow for filtering (based on values in person) in the future. Adding this parameter in your getPeople() method signature is optional, but the rest of this tutorial assumes you have done this.

Now open test/service/**/service/PersonManagerTest.java and add a testGetPeople method:


    public void testGetPeople() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

In order for these tests to compile, you need to add the getPeople() method to the PersonDao and PersonManager interfaces, and their implementations.

Add getPeople() method to DAO and Manager [#4]

Open src/dao/**/dao/PersonDao.java and add the getPeople() method signature:


    public List getPeople(Person person);

Now add the same method signature to src/service/**/service/PersonManager.java. Save all your files and adjust the imports in your tests. Next you need to implement the getPeople() method in your implementation classes. Open src/dao/**/dao/hibernate/PersonDaoHibernate.java and add the following method:


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

You'll notice here that nothing is being done with the person parameter. This is just a placeholder for now - in the future you may want to filter on it's properties using Hibernate's Query Language (HQL) or using Criteria Queries.

An example using a Criteria Query:


    // filter on properties set in the person object
    HibernateCallback callback = new HibernateCallback() {
        public Object doInHibernate(Session sessionthrows HibernateException {
            Example ex = Example.create(person).ignoreCase().ignoreZeroes()
                                .enableLike(MatchMode.ANYWHERE);
            return session.createCriteria(Person.class).add(ex).list();
        }
    };
    return (ListgetHibernateTemplate().execute(callback);

Now implement the getPeople() method in src/service/**/impl/PersonManagerImpl.java:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    }

After saving all your changes, you should be able to run both tests by executing the following:

If everything works - nice job! Now you need to add this retrieve all functionality to the web tier.

Add testSearch() method to Action Test [#5]

Open test/web/**/action/PersonActionTest.java and add the following method:


    public void testSearch() {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Search");
        actionPerform();

        verifyForward("list");

        assertNotNull(getRequest().getAttribute(Constants.PERSON_LIST));
        verifyNoActionErrors();
    }

This class will not compile until you add the PERSON_LIST variable to the src/dao/**/Constants.java file.

I usually copy a similar variable that already exists in this file - i.e. USER_LIST.


    /**
     * The request scope attribute that holds the person list
     */
    public static final String PERSON_LIST = "personList";

Now save all your changes. You won't be able to run ant test-web -Dtestcase=PersonAction yet since PersonAction.search() does not exist (yet).

Add search method to Action [#6]

Open src/web/**/action/PersonAction.java and add the following XDoclet tag at the top - to forward to our list screen.


 * @struts.action-forward name="list" path="/WEB-INF/pages/personList.jsp"

Now add the search method to the body of the PersonAction class.

I used UserAction.search() as a template for this method.


    public ActionForward search(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
            throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'search' method");
        }

        PersonManager mgr = (PersonManagergetBean("personManager");
        List people = mgr.getPeople(null);
        request.setAttribute(Constants.PERSON_LIST, people);

        // return a forward to the person list definition
        return mapping.findForward("list");
    }

Run ant test-web -Dtestcase=PersonAction.

Nice! BUILD SUCCESSFUL
Total time: 1 minute 26 seconds

Create personList.jsp and Canoo test [#7]

Open the personList.jsp file in web/pages. You'll probably want to change the code to show the plural form of the items you're listing. The generated name in this example is "persons" and it should probably be people. At or near line 31, you should have the following line:
<display:setProperty name="paging.banner.items_name" value="persons"/>

Change it to:

<display:setProperty name="paging.banner.items_name" value="people"/>

Finally, add the title and heading keys (personList.title and personList.heading) to web/WEB-INF/classes/ApplicationResources.properties. Open this file and add the following:

# -- person list page --
personList.title=Person List
personList.heading=All People

As a reminder, the personList.title is what ends up in the brower's title bar (the <title> tag) and personList.heading will be put into an <h1> tag before any page content.

At this point, you should be able to run ant clean deploy, start Tomcat and view this page in your browser at http://localhost:8080/appfuse/editPerson.html?method=Search.

Now that we have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In src/web/**/action/PersonAction.java, change the mapping.findForward("mainMenu") in the save, delete and cancel methods to be:


    return mapping.findForward("viewPeople");

You will also need to change verifyForward("mainMenu") to be verifyForward("viewPeople") in the testRemove method of test/web/**/action/PersonActionTest.java. Lastly, the Canoo tests "AddPerson" and "DeletePerson" need to be updated. Open test/web/web-tests.xml and change the following line in the "AddPerson" target:

<verifytitle description="Main Menu appears if save successful" 
    text=".*${mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="Person List appears if save successful" 
    text=".*${personList.title}.*" regex="true"/>

Then in the "DeletePerson" target, change the following line:

<verifytitle description="display Main Menu" 
    text=".*$(mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>

Finally, declare the viewPeople forward in metadata/web/global-forwards.xml after viewUsers as below:

<forward name="viewPeople" path="/editPerson.html?method=Search"/>

The name "viewPeople" is used instead of "list" so that the search method will be executed, rather than simply forwarding to the personForm.jsp (which the "list" forward points to).

To test that displaying this page works, create a new JSP test in test/web/web-tests.xml:


    <!-- Verify the people list screen displays without errors -->
    <target name="SearchPeople" 
        description="Tests search for and displaying all people">
        <webtest name="searchPeople">
            &config;
            <steps>
                &login;

                <invoke description="click View People link" url="/editPerson.html?method=Search"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

You'll also want to add the "SearchPeople" target to the "PersonTests" target so it will be executed along with all the other person-related tests.


    <!-- runs person-related tests -->
    <target name="PersonTests" 
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

Now you can run ant test-canoo -Dtestcase=SearchPeople (or ant test-jsp if Tomcat isn't running) and hopefully it will result in "BUILD SUCCESSFUL". If so - nice work!

Add link to menu [#8]

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in web/pages/mainMenu.jsp:

NOTE: The other links in mainMenu.jsp don't use so this JSP can be shared among the various web framework implementations in AppFuse (i.e. Spring MVC and WebWork).


    <li>
        <html:link forward="viewPeople">
            <fmt:message key="menu.viewPeople"/>
        </html:link>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources.properties.

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:


<Menu name="PeopleMenu" title="menu.viewPeople" forward="viewPeople"/>

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add this new menu to web/common/menu.jsp - which should now look as follows:


<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

Now if you run ant clean deploy start Tomcat and go to http://localhost:8080/appfuse/mainMenu.html, you should see something like the screenshot below.

new-menu-item.png

Notice that there is a new link on the left side (from mainMenu.jsp) and on the right in our menu (from menu.jsp).

That's it!

You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and Struts - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, stop tomcat and run ant clean test-all. This will run all the unit tests within your project. As a reminder, it should be easy to setup and test AppFuse from scratch using ant setup-db setup-tomcat test-all. Also, if you're looking for more robust examples - checkout Struts Resume.

Happy Day!

BUILD SUCCESSFUL
Total time: 2 minutes 31 seconds

ValidationAndListJSF

Part IV: Adding Validation and List Screen - Explains validation logic that makes the firstName and lastName required fields. Also shows how to add a list screen to display all person records in the database.
This tutorial depends on Part III: Creating JSF Pages and JSPs.

About this Tutorial

This tutorial will show you how to add Validation logic to the Person object using JSF's Validation framework. You'll also create a list screen using the Display Tag Library to display all the people in the database.
I will tell you how I do stuff in the Real World in text like this.

Table of Contents

View validation logic in personForm.page [#1]

Implementing validation with JSF is quite simple. For server-side validation, all you need to do is add required="true" to your <h:inputText> tags. For client-side validation, you'll need to add a nested <v:commonsValidator> tag. Other validations (besides required) can be specified by nested validation tags in your inputText tags.

The validation integration for the personForm.html page is already done by AppGen in the personForm.page. If you open web/personForm.jsp, you should see the following JSP Tags. I've highlighted the portions that are relevant to validation.

    <h:inputText id="firstName" value="#{personForm.person.firstName}" required="true">
        <v:commonsValidator type="required" arg="#{text['person.firstName']}"/>
    </h:inputText>

The commonsValidator tag was found in David Geary's Core JSF book. AppFuse also contains a custom LabelRenderer for the <h:outputLabel> tag. This render will add asterisks for required fields.

There are a number of different validators you can use, this example just shows a way to validate Strings are entered. The userForm.jsp contains examples of validation e-mail and validating with regular expressions. All input fields generated by AppGen are required by default. You can change this by modifying the extras/appgen/src/**/Form_jsp.xdt file.

View JSP with validation added and test [#2]

To test the current validation configuration for the person form, run ant db-load deploy, and start Tomcat. Then go to http://localhost:8080/appfuse, login and click on the "Edit Person" link on the Main Menu.

If you erase the values in the firstName and lastName fields and click the save button, you should get the following JavaScript alert.

validation-required.png

JSF does not seem to allow injecting the field label into the error message for server-side validation errors. For more information see this mailing list thread.

To make sure things are really working as expected, you can turn off JavaScript and ensure the server-side validation is working. This is easy in Mozilla Firefox (my favorite browser), just go to Tools → Options → Web Features and uncheck "Enable JavaScript". Now if you clear the fields and save the form, you should see the following:

validation-required-nojs.png

Add testGetPeople methods to DAO and Manager Tests [#3]

To create a List screen (also called a master screen), we need to create methods that will return all the rows from our person table. Let's start by adding tests for these methods to our PersonDaoTest and PersonManagerTest classes. I usually name this method getEntities (i.e. getUsers), but you could also use getAll or search - it's really just a matter of personal preference.

Open test/dao/**/dao/PersonDaoTest.java and add a testGetPeople method:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

The reason I'm passing in a person object to the getPeople method is to allow for filtering (based on values in person) in the future. Adding this parameter in your getPeople() method signature is optional, but the rest of this tutorial assumes you have done this.

Now open test/service/**/service/PersonManagerTest.java and add a testGetPeople method:


    public void testGetPeople() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

In order for these tests to compile, you need to add the getPeople() method to the PersonDao and PersonManager interfaces, and their implementations.

Add getPeople() method to DAO and Manager [#4]

Open src/dao/**/dao/PersonDao.java and add the getPeople() method signature:


    public List getPeople(Person person);

Now add the same method signature to src/service/**/service/PersonManager.java. Save all your files and adjust the imports in your tests. Next you need to implement the getPeople() method in your implementation classes. Open src/dao/**/dao/hibernate/PersonDaoHibernate.java and add the following method:


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

You'll notice here that nothing is being done with the person parameter. This is just a placeholder for now - in the future you may want to filter on it's properties using Hibernate's Query Language (HQL) or using Criteria Queries.

An example using a Criteria Query:




    Example example = Example.create(person)
                             .excludeZeroes()    // exclude zero valued properties
                             .ignoreCase();      // perform case insensitive string comparisons
    try {
        return getSession().createCriteria(Person.class)
                           .add(example)
                           .list();
    catch (Exception e) {
        throw new DataAccessException(e.getMessage());
    }
    return new ArrayList();

Now implement the getPeople() method in src/service/**/impl/PersonManagerImpl.java:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    }

After saving all your changes, you should be able to run both tests by executing the following:

If everything works - nice job! Now you need to add this retrieve all functionality to the web tier.

Create PersonListTest [#5]

In the last couple of tutorials, you've been working with the PersonForm to interact with your form JSP. Now you need to create a new Managed Bean that'll simply handle getting and displaying a list of people in the database.

To begin, create test/web/**/action/PersonListTest.java add a test method for searching.


package org.appfuse.webapp.action;

public class PersonListTest extends BasePageTestCase {
    private PersonList bean;

    protected void setUp() throws Exception {    
        super.setUp();
        bean = (PersonListgetManagedBean("personList");
    }

    protected void tearDown() throws Exception {
        super.tearDown();
        bean = null;
    }

    public void testSearch() throws Exception {
        assertTrue(bean.getPeople().size() >= 1);
        assertFalse(bean.hasErrors());
    }
}

This class will not compile until you create the PersonList class.

Create PersonList [#6]

Create src/web/**/action/PersonList.java. It should read as follows:


package org.appfuse.webapp.action;

import java.io.Serializable;
import java.util.List;

import org.appfuse.service.PersonManager;

public class PersonList extends BasePage implements Serializable {
    private PersonManager personManager;

    public void setPersonManager(PersonManager manager) {
        this.personManager = manager;
    }
    
    public List getPeople() {
        return personManager.getPeople(null);
    }
}

Now you need to add the managed-bean definition for the PersonList class. Open web/WEB-INF/faces-config.xml and add the following managed-bean definition:


    <managed-bean>
        <managed-bean-name>personList</managed-bean-name>
        <managed-bean-class>org.appfuse.webapp.action.PersonList</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>personManager</property-name>
            <value>#{personManager}</value>
        </managed-property>
    </managed-bean>

If you run ant test-web -Dtestcase=PersonList, the test should pass.

Nice! BUILD SUCCESSFUL
Total time: 14 seconds

Create people.jsp and Canoo test [#7]

There should already be a persons.jsp file in your web directory. If not, you can create it using AppGen. From the command-line, navigate to extras/appgen and run ant -Dobject.name=Person -Dappgen.type=pojo. This will generate a Persons.jsp file in extras/appgen/build/gen/web.

Copy PersonList.jsp to web and rename it to people.jsp. Open it for for editing. There are two references to "personList.persons". Change those to be "personList.people".

<h:dataTable var="person" value="#{personList.people}" style="display:none"/>
    
<display:table name="${personList.people}" cellspacing="0" cellpadding="0"
    id="persons" pagesize="25" styleClass="list" 
    export="true" requestURI="">

You can also change the table's "id" and the associated JavaScript that refers to it. This change is not as important as the previous one.

You should already have the title and heading keys (personList.title and personList.heading) for the personList.html page in web/WEB-INF/classes/ApplicationResources.properties. You should've added these keys in the previous tutorial.

Now you need to add the navigation-rules for this page (near the top of web/WEB-INF/faces-config.xml):


    <navigation-rule>
        <from-view-id>/people.jsp</from-view-id>
        <navigation-case>
            <from-outcome>add</from-outcome>
            <to-view-id>/personForm.jsp</to-view-id>
        </navigation-case>
        <navigation-case>
            <from-outcome>edit</from-outcome>
            <to-view-id>/personForm.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>

At this point, you should be able to run ant clean deploy, start Tomcat and view this page in your browser at http://localhost:8080/appfuse/people.html.

Now that you have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In the faces-config.xml file, change the references for the personForm.jsp from "mainMenu" to "people".


    <navigation-rule>
        <from-view-id>/personForm.jsp</from-view-id>
        <navigation-case>
            <from-outcome>cancel</from-outcome>
            <to-view-id>/people.jsp</to-view-id>
            <redirect/>
        </navigation-case>
        <navigation-case>
            <from-outcome>list</from-outcome>
            <to-view-id>/people.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    </navigation-rule>

In the last tutorial, the Canoo tests verify that the user sees the Main Menu after saving/editing a person. Since you've changed that navigation-rule you need to update the tests. Open test/web/web-tests.xml and change any "mainMenu" references in the "PersonsTests" to be go to the people list.

<verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>

Should become:

<verifytitle description="Person List appears if save successful" text=".*${personList.title}.*" regex="true"/>

You will also need to change the Canoo tests to use the list screen for editing, rather than the Main Menu's "Edit Person" link. Change the <clicklink> in "EditPerson", "SavePerson" and "DeletePerson" from

<clicklink label="Edit Person"/>

to:

             
<invoke description="View People" url="/people.html"/>
<clicklink description="edit first record in list" label="1"/>

To test that displaying this page works, create a new HTML test in test/web/web-tests.xml:


    <!-- Verify the people list screen displays without errors -->
    <target name="SearchPeople" 
        description="Tests search for and displaying all people">
        <webtest name="searchPeople">
            &config;
            <steps>
                &login;
                <invoke description="click View People link" url="/people.html"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

You'll also want to add the "SearchPeople" target to the "PersonTests" target so it will be executed along with all the other person-related tests.


    <!-- runs person-related tests -->
    <target name="PersonTests" 
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

Now you can run ant test-canoo -Dtestcase=SearchPeople (or ant test-jsp if Tomcat isn't running) and hopefully it will result in "BUILD SUCCESSFUL". If so - nice work!

Add link to menu [#8]

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in web/pages/mainMenu.html:


    <li>
        <a href="people.html"><fmt:message key="menu.viewPeople"/></a>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources.properties.

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:


<Menu name="PeopleMenu" title="menu.viewPeople" page="/people.html"/> 

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add this new menu to web/common/menu.jsp - which should now look as follows:



<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

Now if you run ant clean deploy start Tomcat and go to http://localhost:8080/appfuse/mainMenu.html, you should see something like the screenshot below.

ValidationAndList/new-menu-item.png

Notice that there is a new link on the left side (from mainMenu.jsp) and on the right in our menu (from menu.jsp).

That's it!

You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and JSF - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, stop tomcat and run ant clean test-all. This will run all the unit tests within your project. As a reminder, it should be easy to setup and test AppFuse from scratch using ant setup.

Happy Day!

BUILD SUCCESSFUL
Total time: 2 minutes 40 seconds

ValidationAndListSpring

Part V: Adding Validation and List Screen - Adding validation logic to the Person object so that firstName and lastName are required fields and adding a list screen to display all person records in the database.
This tutorial depends on Part III: Creating Controllers and JSPs.

About this Tutorial

This tutorial will show you how to add Validation logic (client and server-side) to the Person object using Commons Validator. We'll also create a list screen using the Display Tag Library to display all the people in the database.
I will tell you how I do stuff in the Real World in text like this.

Table of Contents

Add XDoclet Validator to Person.java [#1]

To use Commons Validator with Spring MVC, normally you have to write a validation.xml file by hand. However, thanks to XDoclet, it's much easier - you just need to add a couple of @spring.validator tags to our POJO (Person.java). Let's open it up (src/dao/**/dao/Person.java) and modify your setFirstName and setLastName methods to resemble the following:


    /**
     * @spring.validator type="required"
     */
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * @spring.validator type="required"
     */
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

I should mention that you can also add a msgkey attribute to this tag to override the default message key for this error.


@spring.validator type="required" msgkey="errors.required"

The default key for type="required" is already errors.required, so I usually leave it to the default. This key is defined in web/WEB-INF/classes/ApplicationResources_*.properties.

Now if you save Person.java and run ant clean webdoclet, a validation.xml file will be generated in build/appfuse/WEB-INF/. It's contents should have now have an entry for "person".


      <form name="person">
              <field property="firstName"
                     depends="required">

                  <arg0 key="person.firstName"/>
              </field>
              <field property="lastName"
                     depends="required">

                  <arg0 key="person.lastName"/>
              </field>
      </form>

To enable validation in personForm.jsp, you'll need to make sure your personForm.jsp has the following at the bottom:

<html:javascript formName="person" cdata="false"
      dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
      src="<c:url value="/scripts/validator.jsp"/>"></script>

You'd also need to uncomment the "validator" property we commented out earlier. Make sure the "personFormController" <bean> in web/WEB-INF/action-servlet.xml has the following XML:


    <bean id="personFormController" class="org.appfuse.webapp.action.PersonFormController">
        <property name="commandName"><value>person</value></property>
        <property name="commandClass"><value>org.appfuse.model.Person</value></property>
        <property name="validator"><ref bean="beanValidator"/></property>
        <property name="formView"><value>personForm</value></property>
        <property name="successView"><value>mainMenu.html</value></property>
        <property name="personManager"><ref bean="personManager"/></property>
    </bean>

View JSP with validation added and test [#2]

Now that we have Validation configured for this form, let's make sure it works. Run ant db-load deploy, start Tomcat and go to http://localhost:8080/appfuse/editPerson.html?id=1. One thing you might notice is that the "First Name" and "Last Name" labels now have asterisks next to them - indicating required fields. This is achieved with a LabelTag that looks up the validation rules from Commons Validator.

If you erase the values in the firstName and lastName fields and click the save button, you should get the following JavaScript alert.

ValidationAndList/validation-required.png

To make sure things are really working as expected, you can turn off JavaScript and ensure the server-side validation is working. This is easy in Mozilla Firebird (my favorite browser), just go to Tools → Options → Web Features and uncheck "Enable JavaScript". Now if you clear the fields and save the form, you should see the following:

ValidationAndList/validation-required-nojs.png

If you only want server-side validation (no JavaScript), you can remove the onsubmit attribute of <html:form> (in web/pages/personForm.jsp) as well as the Validator JavaScript tags at the bottom of the page.


<html:javascript formName="personForm" cdata="false"
      dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
      src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

Add testGetPeople methods to DAO and Manager Tests [#3]

To create a List screen (also called a master screen), we need to create methods that will return all the rows from our person table. Let's start by adding tests for these methods to our PersonDaoTest and PersonManagerTest classes. I usually name this method getEntities (i.e. getUsers), but you could also use getAll or search - it's really just a matter of personal preference.

Open test/dao/**/dao/PersonDaoTest.java and add a testGetPeople method:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

The reason I'm passing in a person object to the getPeople method is to allow for filtering (based on values in person) in the future. Adding this parameter in your getPeople() method signature is optional, but the rest of this tutorial assumes you have done this.

Now open test/service/**/service/PersonManagerTest.java and add a testGetPeople method:


    public void testGetPeople() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

In order for these tests to compile, you need to add the getPeople() method to the PersonDao and PersonManager interfaces, and their implementations.

Add getPeople() method to DAO and Manager [#4]

Open src/dao/**/dao/PersonDao.java and add the getPeople() method signature:


    public List getPeople(Person person);

Now add the same method signature to src/service/**/service/PersonManager.java. Save all your files and adjust the imports in your tests. Next you need to implement the getPeople() method in your implementation classes. Open src/dao/**/dao/hibernate/PersonDaoHibernate.java and add the following method:


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

You'll notice here that nothing is being done with the person parameter. This is just a placeholder for now - in the future you may want to filter on it's properties using Hibernate's Query Language (HQL) or using Criteria Queries.

An example using a Criteria Query:


    Example example = Example.create(person)
                             .excludeZeroes()    // exclude zero valued properties
                             .ignoreCase();      // perform case insensitive string comparisons
    try {
        return getSession().createCriteria(Person.class)
                           .add(example)
                           .list();
    catch (Exception e) {
        throw new DataAccessException(e.getMessage());
    }
    return new ArrayList();

Now implement the getPeople() method in src/service/**/impl/PersonManagerImpl.java:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    }

After saving all your changes, you should be able to run both tests by executing the following:

If everything works - nice job! Now you need to add this retrieve all functionality to the web tier.

Create PersonControllerTest [#5]

In the last couple of tutorials, we've been working with the PersonFormController to interact with our HTML form. Now we need to create a new Controller that'll simply handle getting and displaying a list of people in the database.
You could also use a MultiActionController to handle all your List screens.

To begin, create test/web/**/action/PersonControllerTest.java (extends BaseControllerTestCase) and add the following method:


package org.appfuse.webapp.action;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.appfuse.Constants;
import org.springframework.web.servlet.ModelAndView;

public class PersonControllerTest extends BaseControllerTestCase {
    
    public void testHandleRequest() throws Exception {
        PersonController c = (PersonControllerctx.getBean("personController");
        ModelAndView mav = c.handleRequest((HttpServletRequestnull,
                                           (HttpServletResponsenull);
        Map m = mav.getModel();
        assertNotNull(m.get(Constants.PERSON_LIST));
        assertEquals(mav.getViewName()"personList");
    }
}

This class will not compile until you (1) create the PersonController class and (2) add the PERSON_LIST variable to the src/dao/**/Constants.java file. Some folks have suggested that this class be named PersonControllerList - the name choice is up to you. I prefer Controller and FormController because it tells me one implements Controller while the latter extends SimpleFormController.

I usually copy a similar variable that already exists in this file - i.e. USER_LIST.


    /**
     * The request scope attribute that holds the person list
     */
    public static final String PERSON_LIST = "personList";

Now save all your changes. You won't be able to run ant test-web -Dtestcase=PersonController yet since PersonController does not exist (yet). Let's add it.

Create PersonController [#6]

Create src/web/**/action/PersonController.java. It should implement org.springframework.web.servlet.mvc.Controller and read as follows:


package org.appfuse.webapp.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.appfuse.Constants;
import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class PersonController implements Controller {
    private final Log log = LogFactory.getLog(PersonController.class);
    private PersonManager mgr = null;

    public void setPersonManager(PersonManager personManager) {
        this.mgr = personManager;
    }

    public ModelAndView handleRequest(HttpServletRequest request,
                                      HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("entering 'handleRequest' method...");
        }

        return new ModelAndView("personList", Constants.PERSON_LIST,
                                mgr.getPeople(new Person()));
    }
}

Now add a definition to web/WEB-INF/action-servlet.xml for this Controller.


    <bean id="personController" class="org.appfuse.webapp.action.PersonController">
        <property name="personManager"><ref bean="personManager"/></property>
    </bean>

Now if you run ant test-web -Dtestcase=PersonController, the test should pass. Next, create a URL mapping for this controller. To do this, search for the "urlMapping" bean in web/WEB-INF/action-servlet.xml and add the following line to the "mappings" property:


    <prop key="/people.html">personController</prop>

Create personList.jsp and Canoo test [#7]

Open the personList.jsp file in the web/pages folder of your project. One thing you'll probably want to change is the plural form of the items you're listing. The generated name in this example is "persons" and it should probably be people. At or near line 31, you should have the following line:
<display:setProperty name="paging.banner.items_name" value="persons"/>

Change it to:

<display:setProperty name="paging.banner.items_name" value="people"/>

Finally, add the title and heading keys (personList.title and personList.heading) to web/WEB-INF/classes/ApplicationResources.properties. Open this file and add the following:

# -- person list page --
personList.title=Person List
personList.heading=All People

As a reminder, the personList.title is what ends up in the brower's title bar (the <title> tag) and personList.heading will be put into an <h1> tag before any page content. At this point, you could be able to run "ant deploy", start Tomcat and open the list screen in your browser.

Now that we have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In web/WEB-INF/action-servlet.xml, change the personFormController's successView property to be "people.html".


    <property name="successView"><value>people.html</value></property>

You will also need to update the Canoo tests "AddPerson" and "DeletePerson". Open test/web/web-tests.xml and change the following line in the "AddPerson" target:

<verifytitle description="Main Menu appears if save successful" 
    text=".*${mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="Person List appears if save successful" 
    text=".*${personList.title}.*" regex="true"/>

Then in the "DeletePerson" target, change the following line:

<verifytitle description="display Main Menu" text=".*$(mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>

To test that displaying this page works, you can create a new JSP test in test/web/web-tests.xml:


    <!-- Verify the people list screen displays without errors -->
    <target name="SearchPeople" 
        description="Tests search for and displaying all people">
        <webtest name="searchPeople">
            &config;
            <steps>
                &login;
                <invoke description="click View People link" url="/people.html"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

You'll also want to add the "SearchPeople" target to the "PersonTests" target so it will be executed along with all the other person-related tests.



    <!-- runs person-related tests -->
    <target name="PersonTests" 
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

Now you can run ant deploy test-canoo -Dtestcase=SearchPeople (or ant test-jsp if Tomcat isn't running) and hopefully it will result in "BUILD SUCCESSFUL". If so - nice work!

Add link to menu [#8]

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in web/pages/mainMenu.jsp:


    <li>
        <a href="<c:url value="/people.html"/>"><fmt:message key="menu.viewPeople"/></a>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources.properties.

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:


<Menu name="PeopleMenu" title="menu.viewPeople" page="/people.html"/>

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add this new menu to web/common/menu.jsp - which should now looks as follows:


<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

Now if you run ant clean deploy, start Tomcat and go to http://localhost:8080/appfuse/mainMenu.html, you should see something like the screenshot below.

ValidationAndList/new-menu-item.png

Notice that there is a new link on the left side (from mainMenu.jsp) and on the right in our menu (from menu.jsp).

That's it!

You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and Spring's MVC framework - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, stop tomcat and run ant clean test-all. This will run all the unit tests within your project. As a reminder, it should be easy to setup and test AppFuse from scratch using ant setup-db setup-tomcat test-all. Also, if you're looking for more robust examples - checkout Struts Resume.

Happy Day!

BUILD SUCCESSFUL
Total time: 4 minutes 21 seconds

ValidationAndListTapestry

Part IV: Adding Validation and List Screen - Explains validation logic that makes the firstName and lastName required fields. Also shows how to add a list screen to display all person records in the database.
This tutorial depends on Part III: Creating Tapestry Pages and HTML Templates.

About this Tutorial

This tutorial will show you how to add Validation logic to the Person object using Tapestry's Validation framework. You'll also create a list screen using the contrib:Table component to display all the people in the database.
I will tell you how I do stuff in the Real World in text like this.

Table of Contents

View validation logic in personForm.page [#1]

Implementing validation with Tapestry is quite simple. All you need to do is configure a "validators" property on your TextField components. The validation integration for the personForm.html page is already done by AppGen in the personForm.page. If you open web/pages/personForm.page, you should see the following XML.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC 
    "-//Apache Software Foundation//Tapestry Specification 4.0//EN" 
    "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">

<page-specification class="org.appfuse.webapp.action.PersonForm">
    <inject property="engineService" object="engine-service:page"/>
    <inject property="request" object="service:tapestry.globals.HttpServletRequest"/>
    <inject property="response" object="service:tapestry.globals.HttpServletResponse"/>
    <inject property="personManager" type="spring" object="personManager"/>

    <bean name="delegate" class="org.appfuse.webapp.action.Validator"/>
    <property name="message" persist="flash"/>

    <component id="personForm" type="Form">
        <binding name="delegate" value="ognl:beans.delegate"/>
        <binding name="clientValidationEnabled" value="true"/>
    </component>

    <component id="firstNameField" type="TextField">
        <binding name="value" value="person.firstName"/>
        <binding name="validators" value="validators:required"/>
        <binding name="displayName" value="message:person.firstName"/>
    </component>
    <component id="lastNameField" type="TextField">
        <binding name="value" value="person.lastName"/>
        <binding name="validators" value="validators:required"/>
        <binding name="displayName" value="message:person.lastName"/>
    </component>

</page-specification>

The validation delegate (indicated with <bean name="delegate">) is an extended version of Tapestry's ValidationDelegate. This one marks fields required with an asterisk based on your .page file. It also highlights the field with appropriate messages when validation fails (if JavaScript doesn't catch it first).

There are a number of different validators you can use, this example just shows a way to validate Strings are entered. The userForm.page has examples of an EmailValidator and a PatternValidator. All input fields generated by AppGen are required by default. You can change this by modifying the extras/appgen/src/**/*_page.xdt files.

It's important to note that Tapestry does not automatically validate and return you to the form (when errors occur). You have to manually check for errors in your listener method:


    if (getDelegate().getHasErrors()) {
        return;
    }

View HTML with validation added and test [#2]

Since validation is configured in the page-specification file (a.k.a. personForm.page), whenever a field specifies a "validators" property. For those fields that don't have validation, make sure and use the @Label component for its label. Below is a non-validated field example from web/pages/userForm.html.


    <tr>
        <th>
            <label jwcid="@Label" for="phoneNumber" key="user.phoneNumber"/>
        </th>
        <td>
            <input jwcid="phoneNumberField" type="text" id="phoneNumber"/>
        </td>
    </tr>

To test the current validation configuration for the person form, run ant db-load deploy, and start Tomcat. Then go to http://localhost:8080/appfuse, login and click on the "Edit Person" link on the Main Menu.

If you erase the values in the firstName and lastName fields and click the save button, you should get the following JavaScript alert.

validation-required.png

You can see that Tapestry will only display the first validation error, rather than all of them. If you'd like to change this, I'd search the mailing list archives because I'm not aware of it being an option.

To make sure things are really working as expected, you can turn off JavaScript and ensure the server-side validation is working. This is easy in Mozilla Firefox (my favorite browser), just go to Tools → Options → Web Features and uncheck "Enable JavaScript". Now if you clear the fields and save the form, you should see the following:

validation-required-nojs.png

Add testGetPeople methods to DAO and Manager Tests [#3]

To create a List screen (also called a master screen), we need to create methods that will return all the rows from our person table. Let's start by adding tests for these methods to our PersonDaoTest and PersonManagerTest classes. I usually name this method getEntities (i.e. getUsers), but you could also use getAll or search - it's really just a matter of personal preference.

Open test/dao/**/dao/PersonDaoTest.java and add a testGetPeople method:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

The reason I'm passing in a person object to the getPeople method is to allow for filtering (based on values in person) in the future. Adding this parameter in your getPeople() method signature is optional, but the rest of this tutorial assumes you have done this.

Now open test/service/**/service/PersonManagerTest.java and add a testGetPeople method:


    public void testGetPeople() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

In order for these tests to compile, you need to add the getPeople() method to the PersonDao and PersonManager interfaces, and their implementations.

Add getPeople() method to DAO and Manager [#4]

Open src/dao/**/dao/PersonDao.java and add the getPeople() method signature:


    public List getPeople(Person person);

Now add the same method signature to src/service/**/service/PersonManager.java. Save all your files and adjust the imports in your tests. Next you need to implement the getPeople() method in your implementation classes. Open src/dao/**/dao/hibernate/PersonDaoHibernate.java and add the following method:


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

You'll notice here that nothing is being done with the person parameter. This is just a placeholder for now - in the future you may want to filter on it's properties using Hibernate's Query Language (HQL) or using Criteria Queries.

An example using a Criteria Query:


    Example example = Example.create(person)
                             .excludeZeroes()    // exclude zero valued properties
                             .ignoreCase();      // perform case insensitive string comparisons
    try {
        return getSession().createCriteria(Person.class)
                           .add(example)
                           .list();
    catch (Exception e) {
        throw new DataAccessException(e.getMessage());
    }
    return new ArrayList();

Now implement the getPeople() method in src/service/**/impl/PersonManagerImpl.java:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    }

After saving all your changes, you should be able to run both tests by executing the following:

If everything works - nice job! Now you need to add this retrieve all functionality to the web tier.

Create PersonListTest [#5]

In the last couple of tutorials, you've been working with the PersonForm to interact with your HTML form. Now you need to create a new Page class that'll simply handle getting and displaying a list of people in the database.

To begin, create test/web/**/action/PersonListTest.java and add test methods for editing and searching. The edit() method is a listener method that's invoked when you click to edit a row. This is pretty much the same as the edit() method on PersonForm. The one on PersonForm was mainly added for demonstration purposes. AppGen will won't generate an "edit" listener on the form class if you use it to generate your page classes.


package org.appfuse.webapp.action;

import org.apache.tapestry.engine.RequestCycle;
import org.appfuse.service.Manager;
import org.appfuse.model.Person;

import java.util.HashMap;
import java.util.Map;

public class PersonListTest extends BasePageTestCase {
    private PersonList page;

    protected void onSetUp() throws Exception {
        super.onSetUp();
        // these can be mocked if you want a more "pure" unit test
        Map map = new HashMap();
        map.put("personManager"(ManagerapplicationContext.getBean("personManager"));
        page = (PersonListgetPage(PersonList.class, map);
    }

    protected void onTearDownAfterTransaction() throws Exception {
        super.onTearDown();
        page = null;
    }

    public void testEdit() throws Exception {
        RequestCycle cycle = new MockRequestCycle();
        cycle.setServiceParameters(new Object[] {new Long("1")});
        page.edit(cycle);
        assertFalse(page.hasErrors());
    }

    public void testSearch() throws Exception {
        assertTrue(page.getPersons().size() >= 1);
    }
}

This class will not compile until you create the PersonList class.

Create PersonList [#6]

Create src/web/**/action/PersonList.java. It should implement PageBeginRenderListener and read as follows:


package org.appfuse.webapp.action;

import java.util.List;

import org.apache.tapestry.IRequestCycle;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;

public abstract class PersonList extends BasePage {
    public abstract PersonManager getPersonManager();

    public List getPersons() {
        return getPersonManager().getPeople();
    }
    
    public void edit(IRequestCycle cycle) {
        Object[] parameters = cycle.getListenerParameters();
        Long personId = (Longparameters[0];

        if (log.isDebugEnabled()) {
            log.debug("fetching person with personId: " + personId);
        }

        Person person = getPersonManager().getPerson(personId);

        PersonForm nextPage = (PersonFormcycle.getPage("personForm");
        nextPage.setPerson(person);
        cycle.activate(nextPage);
    }
}

Now if you run ant test-web -Dtestcase=PersonList, the test should pass.

Nice! BUILD SUCCESSFUL
Total time: 10 seconds

Create personList.html and Canoo test [#7]

There should already be a personList.html file in web/pages. If not, you can create it using AppGen. From the command-line, navigate to extras/appgen and run ant -Dobject.name=Person -Dappgen.type=pojo. This will generate both a personList.html and a personList.page in extras/appgen/build/gen/web/pages.

Copy personList.html and personList.page to web/pages and rename them people.html and people.page. Open them both for for editing. In personList.html, there should be a <table> with a "source" attribute of "ognl:persons". Change this to "ognl:people":

source="ognl:people"

This is necessary so OGNL (Tapestry's expression language) will call getPeople() on your PersonList class. There's also a "columns" attribute in this table that needs a bit of tweaking.

columns="person.id:id,person.firstName:firstName,person.lastName:lastName,"

The last comma is unecessary (but doesn't seem to effect anything). Remove it so you don't ask yourself why it's there later.

You should already have the title and heading keys (personList.title and personList.heading) for the personList.html page in web/WEB-INF/classes/ApplicationResources.properties. You should've added these keys in the previous tutorial. The column names automatically resolve to i18n messages based on their identifier (with the . in them).

Now add the page entry to web/WEB-INF/tapestry.application so that Tapestry knows where your pages and their specifications reside.

<page name="people" specification-path="pages/people.page"/>

At this point, you should be able to run ant clean deploy, start Tomcat and view this page in your browser at http://localhost:8080/appfuse/people.html.

Now that you have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In the src/web/org/**/action/PersonForm.java class, change the references to MainMenu/mainMenu to be PersonList/people.

In the last tutorial, the Canoo tests verify that the user sees the Main Menu after saving/editing a person. Since you've changed that logic, and now you're activating the PersonList, you need to update the tests. Open test/web/web-tests.xml and change any "mainMenu" references in the "PersonsTests" to be go to the people list.

<verifyTitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>

Should become:

{{{<verifyTitle description="Person List appears if save successful" text=".*${personList.title}.*" regex="true"/>
}}}

You will also need to change the Canoo tests to use the list screen for editing, rather than the Main Menu's "Edit Person" link. Change the <clicklink> in "EditPerson", "SavePerson" and "DeletePerson" from

<clicklink label="Edit Person"/>

to:

<invoke description="View People" url="/people.html"/>
<clicklink description="edit first record in list" label="1"/>

To test that displaying this page works, create a new HTML test in test/web/web-tests.xml:


    <!-- Verify the people list screen displays without errors -->
    <target name="SearchPeople" 
        description="Tests search for and displaying all people">
        <webtest name="searchPeople">
            &config;
            <steps>
                &login;
                <invoke description="click View People link" url="/people.html"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

You'll also want to add the "SearchPeople" target to the "PersonTests" target so it will be executed along with all the other person-related tests.


    <!-- runs person-related tests -->
    <target name="PersonTests" 
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

Now you can run ant test-canoo -Dtestcase=SearchPeople (or ant test-html if Tomcat isn't running) and hopefully it will result in "BUILD SUCCESSFUL". If so - nice work!

Add link to menu [#8]

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in web/pages/mainMenu.html:


    <li>
        <a href="people.html"><span key="menu.viewPeople"/></a>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources.properties.

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:


<Menu name="PeopleMenu" title="menu.viewPeople" page="/people.html"/>

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add this new menu to web/common/menu.jsp - which should now look as follows:


<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

Now if you run ant clean deploy start Tomcat and go to http://localhost:8080/appfuse/mainMenu.html, you should see something like the screenshot below.

ValidationAndList/new-menu-item.png

Notice that there is a new link on the left side (from mainMenu.html) and on the right in our menu (from menu.jsp).

That's it!

You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and Tapestry - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, stop tomcat and run ant clean test-all. This will run all the unit tests within your project. As a reminder, it should be easy to setup and test AppFuse from scratch using ant setup-db setup-tomcat test-all.

Happy Day!

BUILD SUCCESSFUL
Total time: 2 minutes 15 seconds

ValidationAndListWebWork

Part IV: Adding Validation and List Screen - Adding validation logic to the Person object so that firstName and lastName are required fields and adding a list screen to display all person records in the database.
This tutorial depends on Part III: Creating WebWork Actions and JSPs.

About this Tutorial

This tutorial will show you how to add Validation logic to the Person object using WebWork's Validation framework. You'll also create a list screen using the Display Tag Library to display all the people in the database.
I will tell you how I do stuff in the Real World in text like this.

Table of Contents

Create a Person-validation.xml file with validation rules [#1]

To implement validation in WebWork's validation framework, there are a couple things you need to do. The first is create a validation.xml file, the second is add a validation interceptor reference to the action you want to validate. More detailed information on WebWork's validation can be found in WebWork's Validation documentation.

WebWork allows two types of validation - per-action and model-based. Since you likely want the same rules applied for the person object across different actions, this tutorial will use model-based.

Create a new file named Person-validation.xml in the src/dao/**/model directory. It should contain the following XML:


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
    <field name="person.firstName">
        <field-validator type="requiredstring">
            <message key="errors.required"/>
        </field-validator>
    </field>
    <field name="person.lastName">
        <field-validator type="requiredstring">
            <message key="errors.required"/>
        </field-validator>
    </field>
</validators>

The "errors.message" key in ApplicationResources_*.properties (listed below) will use the field's "name" attribute to do internationalization. You can also give the <message> element a body if you don't need i18n.


errors.required=${getText(fieldName)} is a required field.

Now you need to configure PersonAction to know about visitor validation. To do this, create a PersonAction-validation.xml file in the same directory as PersonAction. Fill it with the following XML:


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
    <field name="person">
        <field-validator type="visitor">
            <param name="appendPrefix">false</param>
            <message/>
        </field-validator>
    </field>
</validators>

Unfortunately, WebWork doesn't have a transparent mechanism for reading from the Person-validation.xml file and marking fields as required on the UI. AppFuse's Struts and Spring versions use a LabelTag that makes this possible, but they also both use Commons Validator. It is my hope to someday provide this same functionality for WebWork. In the meantime, the JSP tags "required" attribute has nothing to with the validation rules you specify. Rather, they simply add an asterisk to the label with no further functionality.

NOTE: Client-side validation of model-based validation rules doesn't work with the SpringObjectFactory that AppFuse uses. Furthermore, I believe that WebWork's client-side validation needs some additional features, namely: allow cancelling and showing all errors in one dialog. Because of this, only server-side validation works in WebWork+AppFuse. If you'd like, you can read more about my frustrations with client-side validation.

As a workaround, you can use per-action validation. Just copy the Person-validation.xml file to the "webapp.action" package and rename it to PersonAction-validation.xml.

WebWork's validation interceptor is enabled by default, so you don't need to configure anything for validation to work.

View JSP with validation added and test [#2]

After saving all your files and deploying, validation should kick in when you try to save this form. To test, run ant db-load deploy, start Tomcat and go to http://localhost:8080/appfuse/editPerson.html?id=1.

If you erase the values in the firstName and lastName fields and click the save button, you should see the following:

ValidationAndList/validation-required-nojs.png

Add testGetPeople methods to DAO and Manager Tests [#3]

To create a List screen (also called a master screen), we need to create methods that will return all the rows from our person table. Let's start by adding tests for these methods to our PersonDaoTest and PersonManagerTest classes. I usually name this method getEntities (i.e. getUsers), but you could also use getAll or search - it's really just a matter of personal preference.

Open test/dao/**/dao/PersonDaoTest.java and add a testGetPeople method:


    public void testGetPeople() {
        person = new Person();
        List results = dao.getPeople(person);
        assertTrue(results.size() 0);
    }

The reason I'm passing in a person object to the getPeople method is to allow for filtering (based on values in person) in the future. Adding this parameter in your getPeople() method signature is optional, but the rest of this tutorial assumes you have done this.

Now open test/service/**/service/PersonManagerTest.java and add a testGetPeople method:


    public void testGetPeople() throws Exception {
        List results = new ArrayList();
        person = new Person();
        results.add(person);

        // set expected behavior on dao
        personDao.expects(once()).method("getPeople")
            .will(returnValue(results));

        List people = personManager.getPeople(null);
        assertTrue(people.size() == 1);
        personDao.verify();
    }

In order for these tests to compile, you need to add the getPeople() method to the PersonDao and PersonManager interfaces, and their implementations.

Add getPeople() method to DAO and Manager [#4]

Open src/dao/**/dao/PersonDao.java and add the getPeople() method signature:


    public List getPeople(Person person);

Now add the same method signature to src/service/**/service/PersonManager.java. Save all your files and adjust the imports in your tests. Next you need to implement the getPeople() method in your implementation classes. Open src/dao/**/dao/hibernate/PersonDaoHibernate.java and add the following method:


    public List getPeople(Person person) {
        return getHibernateTemplate().find("from Person");
    }

You'll notice here that nothing is being done with the person parameter. This is just a placeholder for now - in the future you may want to filter on it's properties using Hibernate's Query Language (HQL) or using Criteria Queries.

An example using a Criteria Query:


    Example example = Example.create(person)
                             .excludeZeroes()    // exclude zero valued properties
                             .ignoreCase();      // perform case insensitive string comparisons
    try {
        return getSession().createCriteria(Person.class)
                           .add(example)
                           .list();
    catch (Exception e) {
        throw new DataAccessException(e.getMessage());
    }
    return new ArrayList();

Now implement the getPeople() method in src/service/**/impl/PersonManagerImpl.java:


    public List getPeople(Person person) {
        return dao.getPeople(person);
    }

After saving all your changes, you should be able to run both tests by executing the following:

If everything works - nice job! Now you need to add this retrieve all functionality to the web tier.

Add testSearch() method to Action Test [#5]

Open test/web/**/action/PersonActionTest.java and add the following method:


    public void testSearch() throws Exception {
        assertNull(action.getPeople());
        assertEquals(action.list()"success");
        assertNotNull(action.getPeople());
        assertFalse(action.hasActionErrors());
    }

This class will not compile since the getPeople() and list() methods doesn't yet exist in PersonAction.

Add list() and getPeople() method to Action [#6]

Open src/web/**/action/PersonAction.java and add the list() method to the body of the PersonAction class. While you're at it, add a "people" class variable and a getPeople() method to expose it to the UI.
I used UserAction.search() as a template for this method.


    private List people;

    public List getPeople() {
        return people;
    }
    
    public String list() {
        people = personManager.getPeople(new Person());

        return SUCCESS;
    }

You can test this works by running ant test-web -Dtestcase=PersonAction.

Nice! BUILD SUCCESSFUL
Total time: 10 seconds

Create personList.jsp and Canoo test [#7]

Open the personList.jsp file in web/pages. At the top of the file is a <ww:set> tags that tries to expose the "people" from PersonAction. However, by default, the generated name is just the plural of the object name. So you'll need to change the referenced value from "persons" to "people".


<ww:set name="personList" value="people" scope="request"/>

Another thing you'll probably want to change is the displayed plural form of the items you're listing. The generated name in this example is "persons" and it should be "people". At or near line 30, you should have the following line:

<display:setProperty name="paging.banner.items_name" value="persons"/>

Change it to:

<display:setProperty name="paging.banner.items_name" value="people"/>

Finally, add the title and heading keys (personList.title and personList.heading) to web/WEB-INF/classes/ApplicationResources.properties. Open this file and add the following:

# -- person list page --
personList.title=Person List
personList.heading=All People

As a reminder, the personList.title is what ends up in the brower's title bar (the <title> tag) and personList.heading will be put into an <h1> tag before any page content.

To expose the list() method as a URL, add a new "people" action to web/WEB-INF/classes/xwork.xml:


    <action name="people" class="personAction" method="list"
        <result name="success">/WEB-INF/pages/personList.jsp</result> 
    </action>

At this point, you should be able to run ant clean deploy, start Tomcat and view this page in your browser at http://localhost:8080/appfuse/people.html.

Now that you have a List Screen, let's change the pages that are displayed after adding and deleting a new Person. In web/WEB-INF/classes/xwork.xml, change the savePersons's "input" and "success" results to be "people.html".

You will also need to change the Canoo tests "AddPerson" and "DeletePerson". Open test/web/web-tests.xml and change the following line in the "AddPerson" target:

<verifytitle description="Main Menu appears if save successful" 
    text=".*${mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="Person List appears if save successful" 
    text=".*${personList.title}.*" regex="true"/>

Then in the "DeletePerson" target, change the following line:

<verifytitle description="display Main Menu" 
    text=".*$(mainMenu.title}.*" regex="true"/>

to:

<verifytitle description="display Person List" text=".*${personList.title}.*" regex="true"/>

To test that displaying this page works, create a new JSP test in test/web/web-tests.xml:


    <!-- Verify the people list screen displays without errors -->
    <target name="SearchPeople" 
        description="Tests search for and displaying all people">
        <webtest name="searchPeople">
            &config;
            <steps>
                &login;
                <invoke description="click View People link" url="/people.html"/>
                <verifytitle description="we should see the personList title" 
                    text=".*${personList.title}.*" regex="true"/>
            </steps>
        </webtest>
    </target>

You'll also want to add the "SearchPeople" target to the "PersonTests" target so it will be executed along with all the other person-related tests.


    <!-- runs person-related tests -->
    <target name="PersonTests" 
        depends="SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson"
        description="Call and executes all person test cases (targets)">
        <echo>Successfully ran all Person JSP tests!</echo>
    </target>

Now you can run ant test-canoo -Dtestcase=SearchPeople (or ant test-jsp if Tomcat isn't running) and hopefully it will result in "BUILD SUCCESSFUL". If so - nice work!

Add link to menu [#8]

The last step is to make the list, add, edit and delete functions visible to the user. The simplest way is to add a new link to the list of links in web/pages/mainMenu.jsp:


    <li>
        <a href="<c:url value="/people.html"/>"><fmt:message key="menu.viewPeople"/></a>
    </li>

Where menu.viewPeople is an entry in web/WEB-INF/classes/ApplicationResources.properties.

menu.viewPeople=View People

The other (more likely) alternative is that you'll want to add it to the menu. To do this, add the following to web/WEB-INF/menu-config.xml:


<Menu name="PeopleMenu" title="menu.viewPeople" page="/people.html"/>

Make sure the above XML is inside the <Menus> tag, but not within another <Menu>. Then add this new menu to web/common/menu.jsp - which should now look as follows:


<%@ include file="/common/taglibs.jsp"%>

<div id="menu">
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
    <menu:displayMenu name="AdminMenu"/>
    <menu:displayMenu name="UserMenu"/>
    <menu:displayMenu name="PeopleMenu"/>
    <menu:displayMenu name="FileUpload"/>
    <menu:displayMenu name="FlushCache"/>
    <menu:displayMenu name="Clickstream"/>
</menu:useMenuDisplayer>
</div>

Now if you run ant clean deploy start Tomcat and go to http://localhost:8080/appfuse/mainMenu.html, you should see something like the screenshot below.

ValidationAndList/new-menu-item.png

Notice that there is a new link on the left side (from mainMenu.jsp) and on the right in our menu (from menu.jsp).

That's it!

You've completed the full lifecycle of developing a set of master-detail pages with AppFuse and WebWork - Congratulations! Now the real test is if you can run all the tests in your app without failure. To test, stop tomcat and run ant clean test-all. This will run all the unit tests within your project. As a reminder, it should be easy to setup and test AppFuse from scratch using ant setup-db setup-tomcat test-all. Also, if you're looking for more robust examples - checkout Struts Resume.

Happy Day!

BUILD SUCCESSFUL
Total time: 4 minutes 15 seconds

WebWorkActions

Part III: Creating Actions and JSPs - A HowTo for creating WebWork Actions and JSPs in an AppFuse project.
This tutorial depends on Part II: Creating new Managers.

About this Tutorial

This tutorial will show you how to create WebWork Actions and JSPs. It'll also demonstrate writing a JUnit Test to test PersonAction. The Action we create will talk to the PersonManager we created in the Creating Managers tutorial.
I will tell you how I do stuff in the Real World in text like this.

Let's get started by creating a new Action and JSP in AppFuse's architecture. If you haven't installed the WebWork module at this point, do so by running ant install-webwork.

Table of Contents

Create a skeleton JSP using XDoclet [#1]

In this step, you'll generate a JSP page to display information from the Person object. It will contain WebWork's JSP tags that render table rows for each property in Person.java. The AppGen tool that's used to do this is based off a StrutsGen tool - which was originally written by Erik Hatcher. It's basically just a couple of classes and a bunch of XDoclet templates. All these files are located in extras/appgen.

Here are the simple steps to generating the JSP and a properties file containing the labels for the form elements:

# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
The files in the "pages" directory will end up in "WEB-INF/pages" at deployment time. The container provides security for all files below WEB-INF. This applies to client requests, but not to forwards from the ServletDispatcher. Placing all JSPs below WEB-INF ensures they are only accessed through Actions, and not directly by the client or each other. This allows security to be moved up into the Actions, where it can be handled more efficiently, and out of the base presentation layer.

The web application security for AppFuse specifies that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through an Action to get to a JSP (or at least the ones in pages).

NOTE: If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file. This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:
body#pageName element.class { background-color: blue } 
In the generated JSPs, there are two keys for the title (top of the browser window) and the header (heading in the page). We now need to add these two keys (personDetail.title and personDetail.heading) to ApplicationResources.properties.
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
Just above, we added "personForm.*" keys to this file, so why do I use personForm and personDetail? The best reason is because it gives a nice separation between form labels and text on the page. Another reason is because all the *Form.* give you a nice representation of all the fields in your database.

I recently had a client who wanted all fields in the database searchable. This was fairly easy to do. I just looked up all the keys in ApplicationResources.properties which contained "Form." and then put them into a drop-down. On the UI, the user was able to enter a search term and select the column they wanted to search. I was glad I followed this Form vs. Detail distinction on that project!

Create PersonActionTest to test PersonAction [#2]

To create a JUnit Test for PersonAction, start by creating a PersonActionTest.java file in the test/web/**/action directory.


package org.appfuse.webapp.action;

import org.springframework.mock.web.MockHttpServletRequest;

import com.opensymphony.webwork.ServletActionContext;

public class PersonActionTest extends BaseActionTestCase {
    private PersonAction action;

    protected void setUp() throws Exception {    
        super.setUp();
        action = (PersonActionctx.getBean("personAction");
    }
    
    protected void tearDown() throws Exception {
        super.tearDown();
        action = null;
    }
    
    public void testEdit() throws Exception {
        log.debug("testing edit...");
        action.setId("1");
        assertNull(action.getPerson());
        assertEquals("success", action.edit());
        assertNotNull(action.getPerson());
        assertFalse(action.hasActionErrors());
    }

    public void testSave() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        ServletActionContext.setRequest(request);
        action.setId("1");
        assertEquals(action.edit()"success");
        assertNotNull(action.getPerson());
        
        // update last name and save
        action.getPerson().setLastName("Updated Last Name");
        assertEquals("input", action.save());
        assertEquals("Updated Last Name", action.getPerson().getLastName());
        assertFalse(action.hasActionErrors());
        assertFalse(action.hasFieldErrors());
        assertNotNull(request.getSession().getAttribute("messages"));
    }

    public void testRemove() throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        ServletActionContext.setRequest(request);
        action.setDelete("");
        Person person = new Person();
        person.setId(new Long(2));
        action.setPerson(person);
        assertEquals("success", action.delete());
        assertNotNull(request.getSession().getAttribute("messages"));
    }
}

Nothing will compile at this point because you need to create the PersonAction that you're referring to in this test.

Create PersonAction [#3]

In src/web/**/action, create a PersonAction.java file with the following contents:


package org.appfuse.webapp.action;

import java.util.ArrayList;
import java.util.List;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;

public class PersonAction extends BaseAction {
    private Person person;
    private String id;
    private PersonManager personManager;

    public void setId(String id) {
        this.id = id;
    }

    public void setPersonManager(PersonManager manager) {
        this.personManager = manager;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public String delete() {
        personManager.removePerson(String.valueOf(person.getId()));
        saveMessage(getText("person.deleted"));

        return SUCCESS;
    }

    public String edit() {
        if (id != null) {
            person = personManager.getPerson(id);
        else {
            person = new Person();
        }

        return SUCCESS;
    }

    public String save() throws Exception {
        if (cancel != null) {
            return "cancel";
        }

        if (delete != null) {
            return delete();
        }

        boolean isNew = (person.getId() == null);

        personManager.savePerson(person);

        String key = (isNew"person.added" "person.updated";
        saveMessage(getText(key));

        if (!isNew) {
            return INPUT;
        else {
            return SUCCESS;
        }
    }
}

There are a few keys you (might) need to add to ApplicationResources.properties to display the success messages. This file is located in web/WEB-INF/classes - open it and add the following:

I usually add these under the # -- success messages -- comment.
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

You could use generic added, deleted and updated messages, whatever works for you. It's nice to have separate messages in case these need to change on a per-entity basis.

You might notice that the code we're using to call the PersonManager is the same as the code we used in our PersonManagerTest. Both PersonAction and PersonManagerTest are clients of PersonManagerImpl, so this makes perfect sense.

Now you need to tell Spring and WebWork that this action exists. First, add a bean definition for PersonAction to web/WEB-INF/action-servlet.xml:


    <bean id="personAction" class="org.appfuse.webapp.action.PersonAction" scope="prototype">
        <property name="personManager" ref="personManager"/>
    </bean>

Then add an entry that refers to this bean in web/WEB-INF/classes/xwork.xml:


    <action name="editPerson" class="personAction" method="edit">
        <result name="success">/WEB-INF/pages/personForm.jsp</result>
    </action>
    
    <action name="savePerson" class="personAction" method="save">
        <result name="cancel" type="redirect">mainMenu.html</result>
        <result name="input">/WEB-INF/pages/personForm.jsp</result>
        <result name="success" type="redirect">mainMenu.html</result>
    </action>

Run the PersonActionTest [#4]

If you look at our PersonActionTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so let's add those records to our sample data file (metadata/sql/sample-data.xml). I'd just add it at the bottom - order is not important since it (currently) does not relate to any other tables.
  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

DBUnit loads this file before we running any of the tests, so this record will be available to your Action test.

Make sure are in the base directory of your project and all files are saved. If you run ant test-web -Dtestcase=PersonAction - everything should work as planned.

BUILD SUCCESSFUL
Total time: 21 seconds

Clean up the JSP to make it presentable [#5]

If you want to add a usability enhancement to your form, you can set the cursor to focus on the first field when the page loads. Simply add the following JavaScript at the bottom of your form:
<script type="text/javascript">
    Form.focusFirstElement(document.forms["personForm"]);
</script>

Now if you execute ant db-load deploy, start Tomcat and point your browser to http://localhost:8080/appfuse/editPerson.html?id=1, you should see something like this, with focus given to the first field in the form.

CreateActions/personForm-final.png

Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, but this can easily be done by adding text (using <fmt:message>) at the top of the personForm.jsp page.

[Optional] Create a Canoo WebTest to test browser-like actions [#6]

The final (optional) step in this tutorial is to create a Canoo WebTest to test the JSPs.
I say this step is optional, because you can run the same tests through your browser.

You can use the following URLs to test the different actions for adding, editing and saving a user.

Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.

I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing -Dtestcase=Name, I've found that I'm used to doing CamelCase for my JUnit Tests.


<!-- runs person-related tests -->
<target name="PersonTests"
    depends="EditPerson,SavePerson,AddPerson,DeletePerson"
    description="Call and executes all person test cases (targets)">
    <echo>Successfully ran all Person JSP tests!</echo>
</target>

<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson"
    description="Tests editing an existing Person's information">
    <webtest name="editPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
        </steps>
    </webtest>
</target>

<!-- Edit a person and then save -->
<target name="SavePerson"
    description="Tests editing and saving a user">
    <webtest name="savePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set lastName" name="person.lastName" value="Canoo"/>
            <clickbutton label="Save" description="Click Save"/>
            <verifytitle description="Page re-appears if save successful"
                text=".*${personDetail.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.updated}"/>
        </steps>
    </webtest>
</target>

<!-- Add a new Person -->
<target name="AddPerson"
    description="Adds a new Person">
    <webtest name="addPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Add Button" url="/editPerson.html"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set firstName" name="person.firstName" value="Abbie"/>
            <setinputfield description="set lastName" name="person.lastName" value="Raible"/>
            <clickbutton label="${button.save}" description="Click button 'Save'"/>
            <verifytitle description="Main Menu appears if save successful"
                text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.added}"/>
        </steps>
    </webtest>
</target>

<!-- Delete existing person -->
<target name="DeletePerson"
    description="Deletes existing Person">
    <webtest name="deletePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
            <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
            <verifyNoDialogResponses/>
            <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.deleted}"/>
        </steps>
    </webtest>
</target>

After adding this, you should be able to run ant test-canoo -Dtestcase=PersonTests with Tomcat running or ant test-jsp -Dtestcase=PersonTests if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.

You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add the following between </webtest> and </target> at the end of each target.

<loadfile property="web-tests.result" 
    srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>
BUILD SUCCESSFUL
Total time: 10 seconds


Next Up: Part IV: Adding Validation and List Screen - Adding validation logic to the personForm so that firstName and lastName are required fields and adding a list screen to display all person records in the database.