Personal Finance Manager Web Application (Part Six)

Content

Web Application

Summary

On the previous page we defined the User Stories and tasks for Sprint Two, we will begin by creating a maven web application.

Creating the Web Application

(Epic 4a Web Application Views - Task i.)

Maven allow us to create a standard web application project, create it using this command.

mvn archetype:generate -DgroupId=com.inivitiv.tutorials.pfm -DartifactId=pfm-webapp -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
Once completed the following directory structure is created...

web-app-dirs-initial.png

It's possible to run this simple web application by building a war file and deploying to Tomcat. For convenience you can get maven to run the web application by adding a simple plugin to the maven pom.xml file. Update the pom.xml file to this version...

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.inivitiv.tutorials.pfm</groupId>
  <artifactId>pfm-webapp</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>pfm-webapp Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>pfm-webapp</finalName>

  <plugins>
    <plugin>
      <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.10</version>
        <configuration>
        		<scanIntervalSeconds>60</scanIntervalSeconds>
        </configuration>
    </plugin>
  </plugins>

  </build>
</project>

This pom.xml now defines a plugin named Jetty that is a small application server that will run your web application from the command line using maven. It will scan your project files every 60 seconds and reload any pages, configuration etc. The pfm-webapp although only just created and not even touched (except the plugin defined above) will indeed run. Just execute this following maven command.

mvn jetty:run

Now point your web browser to the following URL 'http://localhost:8080/pfm-webapp/' the following page is displayed confirming that your simple web application is indeed running...

pfm-webapp-helloworld.png

Of course this is just a basic web application structure. For the pfm-webapp we are going to create a web applicaiton that uses the Spring Framework including the WEB-MVC package. We are also going to use the Velocity templating engine so create the following directory structure.

pfm-webapp-dirs-full.png

Add Project to Version Control

(Epic 1a Account Owners CRUD - Task i)

We are at a good point to add our web application to GIT version control so in the pfm-webapp directory issued the following commands to add all the current files to version control.

git init
git add .
git commit -a -m 'initial import'

(Epic 1a Account Owners CRUD - Task ii)

Now create a feature branch for the Owner CRUD views by issuing this GIT command.

git checkout -b 'owner_crud_views'

Maven Web App Dependencies

Next we will add all the required maven dependencies for our Spring MVC Velocity project, update the pfm-webapp pom.xml to this version.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.inivitiv.tutorials.pfm</groupId>
  <artifactId>pfm-webapp</artifactId>
  <packaging>war</packaging>
  <version>1.0.1</version>
  <name>Personal Finance Manager Web Application</name>
  <url>http://inivitiv.com</url>
  <properties>
        <spring.version>3.0.5.RELEASE</spring.version>
        <jdk.version>1.6</jdk.version>
    </properties>
  <dependencies>
  <!-- inivitiv projects dependencies -->
    <dependency>
        <groupId>com.inivitiv.tutorials.pfm.domain</groupId>
        <artifactId>pfm-model</artifactId>
        <version>1.0.1</version>
    </dependency>
    <dependency>
        <groupId>com.inivitiv.tutorials.pfm.persist</groupId>
        <artifactId>pfm-persistence</artifactId>
        <version>1.0.1</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
            <!-- Spring Dependencies -->
            <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>$|spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>$|spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>$|spring.version}</version>
        </dependency>
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-orm</artifactId>
	    <version>$|spring.version}</version>
	</dependency>
	<!-- Velocity templating engine dependencies -->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity</artifactId>
        <version>1.7</version>
    </dependency>
    <dependency>
        <groupId>velocity-tools</groupId>
        <artifactId>velocity-tools-view</artifactId>
        <version>1.3</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.1</version>
    </dependency>
    <dependency>
        <groupId>commons-digester</groupId>
        <artifactId>commons-digester</artifactId>
        <version>1.7</version>
    </dependency>
    <!-- For org.apache.commons.dbcp.BasicDataSource in pfm-webapp-servlet.xml -->
	<dependency>
	    <groupId>commons-dbcp</groupId>
	    <artifactId>commons-dbcp</artifactId>
	    <version>1.2.2</version>
	</dependency>

  </dependencies>
  <build>
    <finalName>pfm-webapp</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <verbose>true</verbose>
          <fork>true</fork>
          <compilerVersion>$|jdk.version}</compilerVersion>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.10</version>
        <configuration>
          <scanIntervalSeconds>60</scanIntervalSeconds>
        </configuration>
       </plugin>
     </plugins>
  </build>
</project>

Setting up Spring

web.xml

The web.xml file (found in src/main/webapp/WEB-INF) must be updated so that all requests are forwarded to the Spring Servlet. Our web application will use URL's that end with '.htm' so update the web.xml to this version. (click to select)

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <display-name>Personal Finance Web Application</display-name>
  <description>A web application to manage personal finances.</description>

  <!-- The servlet given name, Dispatcher will look for pfm-webapp-servlet.xml in WEB-INF/ folder -->	
  <servlet>
    <servlet-name>pfm-webapp</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet> 
    
  <!-- All PFM web application URL's will end with '.htm' -->	
  <servlet-mapping>
    <servlet-name>pfm-webapp</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.htm</welcome-file>
  </welcome-file-list>
</web-app>

Spring Context

The spring DispatcherServlet defined in our web.xml expects a configuration files for any beans to be placed in the 'WEB-INF' folder and named the same as the servlet name also defined in web.xml it must be named pfm-webapp-servlet.xml. So create this file in the 'WEB-INF' directory.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <!-- the application context definition for pfm-webapp DispatcherServlet -->

  <!-- i18n support for resource bundles -->
  <bean id="messageSource" 
        class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="messages"/>
  </bean>
  
  <!-- Datasource definition for a HSQL file driven database -->
  <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:file:data/pfm"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>
  <!-- Let Spring manage the Hibernate sessions as it does a good job 
        The model beans are annotated so use the AnnotationSessionFactory Bean
        and declare the classes that are annotated
  -->
  <bean id="mySessionFactory" 
      class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <!-- annotated source files -->
    <property name="annotatedClasses">
      <list>
        <value>com.inivitiv.tutorials.pfm.domain.Owner</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.generate_statistics">true</prop>
        <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
        <prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.current_session_context_class">thread</prop>
     </props>
    </property>      
  </bean>
  <!-- Velocity templating engine will decouple our Controller beans from the view layer -->
  <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="prefix">
      <value>/WEB-INF/velocity/</value>
    </property>
    <property name="suffix">
        <value>.vm</value>
    </property>
    <property name="exposeSpringMacroHelpers">
        <value>true</value>
    </property>
  </bean>
  <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="resourceLoaderPath">
       <value>/</value>
    </property>
  </bean>
  <!-- PFM Model and Persistance projecs Bean Declarations these will be injected by Spring as properties of our Controllers -->
  <bean id="ownerDao" class="com.inivitiv.tutorials.pfm.persist.OwnerDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

</beans>
The comments within the source explain each section and its purpose, for more information regarding the Spring <web-app-name>-servlet.xml declarations you can read the API documentation found on the Spring Docs Web Site, just navigate to the release you are using.

Account Owners List View

In this section we will create a Spring controller to obtain all the Account Owners from the database. We will also create a Velocity template to display the results. Using the Spring framework makes this relatively easy.

  1. Create a Spring Controller Java Class.
  2. Create a Velocity Template View.
  3. Update pfm-webapp-servlet.xml with a mapping to the new Controller

The Account Owners List Controller

Create this controller class in the package 'com.inivitiv.tutorials.pfm.web.controller' as 'ListOwnersController.java'

package com.inivitiv.tutorials.pfm.web.controller;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.inivitiv.tutorials.pfm.domain.Owner;
import com.inivitiv.tutorials.pfm.persist.PFMGenericDao;
/**
 * Spring Controller for listing all the Account Owners.
 * @author petersnr
 *
 */
@org.springframework.stereotype.Controller
@RequestMapping("/list.htm")
public class ListOwnersController {
    /**
     * Logger.
     */
    protected final Log logger = LogFactory.getLog(getClass());
    /**
     * Owner DAO will be injected by Spring Framework
     */
    @Autowired
    private PFMGenericDao ownerDao;
    /**
     * Method to list all the Account Owners.
     * @param model The Spring ModelMap
     * @return A String containing the view name.
     */
    @RequestMapping(method=RequestMethod.GET)
    public String handleRequest(ModelMap model){
	// Retrieve all owenrs
	List owners = ownerDao.findAll();
	// Add to Spring MVC Model
	model.put("owners", owners);
        logger.info("Returning Owner list view");

        return "list";
    }
    /**
     * gets the OwnerDao instance.
     * @return OwnerDaoImpl instance
     */
    public PFMGenericDao getOwnerDao() {
        return ownerDao;
    }
    /**
     * sets the OwnerDao instance.
     * @param ownerDao A OwnerDaoImpl instance
     */
    public void setOwnerDao(PFMGenericDao ownerDao) {
        this.ownerDao = ownerDao;
    }
}

Account Owners List Contoller Unit Test

In keeping with the Test Driven Design (TDD) development methodology create this Unit Test in the 'src/test' folder in the package 'com.inivitiv.tutorials.pfm.web.controller', (actually we should really create the test first). As all our Unit Tests will contain common set up and tear down code we will create a base class to contain this behaviour. Here is the listing for the base class 'PFMControllerTestsInit'.

package com.inivitiv.tutorials.pfm.web.controller;

import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.junit.AfterClass;
import org.junit.BeforeClass;

import com.inivitiv.tutorials.pfm.domain.Owner;
import com.inivitiv.tutorials.pfm.persist.OwnerDaoImpl;
/**
 * This class sets up the Hibernate session ready for JUnit tests.
 * All Unit tests that extend this class will use the common
 * setup and tear down that occurs in this class.
 * @author petersnr
 *
 */
public class PFMControllerTestsInit {
    /**
     * All tests require an Owner Dao for DB operations.
     */
    protected static OwnerDaoImpl ownerDaoImpl;
    /**
     * The hibernate SessionFactory.
     */
    private static SessionFactory sessionFactory;
    /**
     * Runs before JUnit class.
     * Sets up the Hibernate Session for the test.
     */
    @BeforeClass
    public static void globalSetup() {
        sessionFactory = buildSessionFactory();
        try {
            sessionFactory.openSession();
            ownerDaoImpl = new OwnerDaoImpl(sessionFactory);
        } catch (HibernateException hbe) {
            hbe.printStackTrace();
        }
    }
    /**
     * Runs after JUnit class.
     */
    @AfterClass
    public static void cleanUp() {
        List all = ownerDaoImpl.findAll();
        Owner owner = null;
        for (Object obj : all) {
            owner = (Owner) obj;
            ownerDaoImpl.delete(owner);
        }
        sessionFactory.close();
    }
    /**
     * Configures the SessionFactory.
     * @return The configured Hibernate SessionFactory instance
     */
    private static SessionFactory buildSessionFactory() {
        try {
            // This will read in the hibernate.cfg.xml file from the classpath
            // and set up the database by reading annotations from the classes
            // defined within the configuration file.
            return new AnnotationConfiguration().configure()
                    .buildSessionFactory();
        } catch (Throwable ex) {
            ex.printStackTrace();
            throw new ExceptionInInitializerError(ex);
        }
    }
}
The JUnit base class carries out @BeforeClass and a @AfterClass operations to set up a test HSQL database connection for the OwnerDaoImple class Session and to clean up afterwards.

Now create the ListOwnersControllerTest.java Unit test...

package com.inivitiv.tutorials.pfm.web.controller;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.ui.ModelMap;

public class ListOwnersControllerTest extends PFMControllerTestsInit {
    /**
     * Logger.
     */
    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * Tests handleRequest method.
     * 
     * @throws Exception
     *             The exception thrown
     */
    @Test
    public void testHandleRequestView() throws Exception {
        ListOwnersController controller = new ListOwnersController();
        controller.setOwnerDao(ownerDaoImpl);
        ModelMap model = new ModelMap();
        String view = controller.handleRequest(model);
        // Check model contains key for array of users.
        logger.info("Testing  ListOwnersController() contains key for owners");
        assertTrue(model.containsKey("owners"));
        // Check model returns correct view
        logger.info("Testing correct view is set for ListOwnersController()");
        assertEquals("list", view);
    }

}
Now run the test by executing.
mvn test
Ensure your test passes by observing the 'BUILD SUCCESS' message once maven has completed.

Now that we know the 'owers' key is present in the model and the correct view is being returned we can create the Velocity template to display the results.

Account Onwers Velocity View Template

Next is a Velocity template for the Account Owners List View. Velocity is a well know templating engine that integrates well with Spring and decouples the view layer well. You can find more about Velocity at on the Apache Velocity Project Site

Create this template file in the 'WEB-INF/velocity' folder as 'list.vm'.

#parse("WEB-INF/velocity/header.vm")
<h2>#springMessage("listowner.heading")</h2>
#if( ${owners.size()} > 0 )
<p>
<table>
<tr><th>ID</th><th>First Name</th><th>Middle Name</th><th>Surname</th></tr>
#foreach(${owner} in ${owners})
    <tr>
        <td>${owner.id}</td>
        <td>${owner.firstName}</td>
        <td>${owner.middleName}</td>
        <td>${owner.surname}</td>
    </tr>
#end
</table>
</p>
#else
<h4>No Account Owners found in Personal Finance Manager</h4>
#end
<a href="#springUrl("#")">#springMessage("addowner.link.text")</a>
#parse("WEB-INF/velocity/footer.vm")
So lets explain this file.
  • The first line actually includes another Velocity template for the header which we will create in a minute.
  • The next line is the page heading that is using a language (I18N) message file to extract the text. This makes it easier to support internationalization of our user interface. We will need to create a messages.properties file for this to work, also create next.
  • Then a Velocity conditional statement checks to see if any Account Owners are found, if true the list is displayed, if not a message is display indicating that none were found.
  • The #foreach statement is a loop that loops through each owner in the owners array and creates a table entry for each one
  • The <a href tag uses a Velocity Spring macro to support links to other URL's within our application.
  • Finally another Velocity macro includes a Velocity template for the footer section of our page.
As mentioned above the text for our web application is stored in a language file and looked up at run time. Create a file named messages.properties in 'src/main/resources and add these contents.
title=Personal Finance Manager
listowner.heading=Account Owners Listing
listowner.link.text=Owner Listing
addowner.link.text=Add New Account Owner
Next we will create the common Velocity templates for the header and footer sections of our page. Create the files header.vm, footer.vm and store them in '/src/main/webapp/WEB-INF/velocity.

Header.vm contents.

<html>
<head>
<style>
#include( "/css/skin.css" )/>
</style> 
</head>
<body>
#if( $XHTML )
  #set( $br = "<br />" )
#else
  #set( $br = "<br>" )
#end
<DIV id="HEADER">
<h1>#springMessage("title")</h1>
</DIV>
Footer.vm contents.
<DIV id="FOOTER">
Personal Finance Manager v1.0.1 - 
<a href="www.inivitiv.com">www.inivitiv.com</a> - 
Author Peter Goudman
</DIV>
</body>
</html>
Looking at the contents of header.vm reveals that we are referencing a cascading style sheet, so create the file /src/main/webapp/css/skin.css with these contents.
body {
    font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif;
}

#HEADER {
    padding:4px 4px;
}
#FOOTER {
    position: absolute; 
    bottom: 0;
    margin: 10px 0 0 0;
    padding: 4px 4px;
    font-size: 10px;
}
h1, h2, h3, h4, h5, h6 {
    color: #585858;
}
a {
    color: blue;
}
a:hover {
    background-color: #ababab;
    color: #D8D8D8;
}
table, th, td {
    padding: 2px 4px;
    border-collapse: collapse;
    border: 1px solid black;
    background-color: white;
    color: black;
}

Spring Servlet List View Mapping

To run our web application and test the view we need to add a mapping to pfm-webapp-servlet.xml. Add this entry to the end of the file just after the 'ownerDao' bean declaration.

  <bean name="/list.htm" class="com.inivitiv.tutorials.pfm.web.controller.ListOwnersController">
    <property name="ownerDao" ref="ownerDao"/>
  </bean>
Now when we start up our web application and enter navigate to 'http://localhost:8080/pfm-webapp/list.htm' the DispatcherServlet will call the ListOwnersController which will get all the owners from the database, add them to the Spring MVC model and then call the 'list' view. The Velocity list view will reference the list of owners in the model and display any found, or display a message that none were found.

Of course we have not added any owners yet so expect the latter message but we will take care of this next. Before starting up PFM web application check that you have the following files and directory structure.

pfm-webapp-dirs2.png

Now start up the web application by executing..

mvn jetty:run
Navigate to 'http://localhost/pfm-webapp/list.htm and the following page will be displayed.

pfm-webapp-first-run.png

As mentioned above, there are no Account Owners in the database and clicking on the 'Add New Account Owner' will do nothing as we have not yet written the code for adding them. Navigate to the next page where the Spring Controller and the page for add new account owners will be explained.

Date Entered2014-06-09