Maven by Example

7.6. The Simple Web Application Module

The web application is defined in a simple-webapp project. This simple web application project is going to define two Spring MVC Controllers: WeatherController and simple-weather and the applicationContext-persist.xml file in simple-persist. The component architecture of this simple web application is shown in Figure 7.3, “Spring MVC Controllers Referencing Components in simple-weather and simple-persist.”.

figs/web/multimodule-web-web-spring.png

Figure 7.3. Spring MVC Controllers Referencing Components in simple-weather and simple-persist.


The POM for simple-webapp is shown in POM for simple-webapp.

POM for simple-webapp. 

<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>
    <parent>
        <groupId>org.sonatype.mavenbook.multispring</groupId>
        <artifactId>simple-parent</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>simple-webapp</artifactId>
    <packaging>war</packaging>
    <name>Simple Web Application</name>
    <dependencies>
        <dependency> (1)
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.4</version>
                <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.sonatype.mavenbook.multispring</groupId>
            <artifactId>simple-weather</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.sonatype.mavenbook.multispring</groupId>
            <artifactId>simple-persist</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.0.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.5</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>simple-webapp</finalName>
        <plugins>
            <plugin> (2)
                    <groupId>org.mortbay.jetty</groupId>
                    <artifactId>maven-jetty-plugin</artifactId>
                    <dependencies> (3)
                            <dependency>
                                <groupId>hsqldb</groupId>
                                <artifactId>hsqldb</artifactId>
                                <version>1.8.0.7</version>
                            </dependency>
                    </dependencies>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId> (4)
                    <artifactId>hibernate3-maven-plugin</artifactId>
                    <version>2.0</version>
                    <configuration>
                        <components>
                            <component>
                                <name>hbm2ddl</name>
                                <implementation>annotationconfiguration</implementation> (5)
                            </component>
                        </components>
                    </configuration>
                    <dependencies>
                        <dependency>
                            <groupId>hsqldb</groupId>
                            <artifactId>hsqldb</artifactId>
                            <version>1.8.0.7</version>
                        </dependency>
                    </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

As this book progresses and the examples become more and more substantial, you’ll notice that the pom.xml begins to take on some weight. In this POM, we’re configuring four dependencies and two plugins. Let’s go through this POM in detail and dwell on some of the important configuration points:

(1)

This simple-webapp project defines four dependencies: the Servlet 2.4 specification, the simple-weather service library, the simple-persist persistence library, and the entire Spring Framework 2.0.7.

(2)

The Maven Jetty plugin couldn’t be easier to add to this project; we simply add a plugin element that references the appropriate groupId and artifactId. The fact that this plugin is so trivial to configure means that the plugin developers did a good job of providing adequate defaults that don’t need to be overridden in most cases. If you did need to override the configuration of the Jetty plugin, you would do so by providing a configuration element.

(3)

In our build configuration, we’re going to be configuring the Maven Hibernate3 Plugin to hit an embedded HSQLDB instance. For the Maven Hibernate 3 plugin to successfully connect to this database using JDBC, the plugin will need reference the HSQLDB JDBC driver on the classpath. To make a dependency available for a plugin, we add a dependency declaration right inside plugin declaration. In this case, we’re referencing hsqldb:hsqldb:1.8.0.7. The Hibernate plugin also needs the JDBC driver to create the database, so we have also added this dependency to its configuration.

(4)

The Maven Hibernate plugin is when this POM starts to get interesting. In the next section, we’re going to run the hbm2ddl goal to generate a HSQLDB database. In this pom.xml, we’re including a reference to version 2.0 of the hibernate3-maven-plugin hosted by the Codehaus Mojo plugin.

(5)

The Maven Hibernate3 plugin has different ways to obtain Hibernate mapping information that are appropriate for different usage scenarios of the Hibernate3 plugin. If you were using Hibernate Mapping XML (.hbm.xml) files, and you wanted to generate model classes using the hbm2java goal, you would set your implementation to configuration. If you were using the Hibernate3 plugin to reverse engineer a database to produce .hbm.xml files and model classes from an existing database, you would use an implementation of jdbcconfiguration. In this case, we’re simply using an existing annotated object model to generate a database. In other words, we have our Hibernate mapping, but we don’t yet have a database. In this usage scenario, the appropriate implementation value is annotationconfiguration. The Maven Hibernate3 plugin is discussed in more detail in the later section Section 7.7, “Running the Web Application”.

Next, we turn our attention to the two Spring MVC controllers that will handle all of the requests. Both of these controllers reference the beans defined in simple-weather and simple-persist.

simple-webapp WeatherController. 

package org.sonatype.mavenbook.web;

import org.sonatype.mavenbook.weather.model.Weather;
import org.sonatype.mavenbook.weather.persist.WeatherDAO;
import org.sonatype.mavenbook.weather.WeatherService;
import javax.servlet.http.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class WeatherController implements Controller {

    private WeatherService weatherService;
    private WeatherDAO weatherDAO;

    public ModelAndView handleRequest(HttpServletRequest request,
                                      HttpServletResponse response) throws Exception {

        String zip = request.getParameter("zip");
        Weather weather = weatherService.retrieveForecast(zip);
        weatherDAO.save(weather);
        return new ModelAndView("weather", "weather", weather);
    }

    public WeatherService getWeatherService() {
        return weatherService;
    }

    public void setWeatherService(WeatherService weatherService) {
        this.weatherService = weatherService;
    }

    public WeatherDAO getWeatherDAO() {
        return weatherDAO;
    }

    public void setWeatherDAO(WeatherDAO weatherDAO) {
        this.weatherDAO = weatherDAO;
    }
}

WeatherController implements the Spring MVC Controller interface that mandates the presence of a handleRequest() method with the signature shown in the example. If you look at the meat of this method, you’ll see that it invokes the retrieveForecast() method on the weatherService instance variable. Unlike the previous chapter, which had a Servlet that instantiated the WeatherService class, the WeatherController is a bean with a weatherService property. The Spring IoC container is responsible for wiring the controller to the weatherService component. Also notice that we’re not using the WeatherFormatter in this Spring controller implementation; instead, we’re passing the Weather object returned by retrieveForecast() to the constructor of ModelAndView. This ModelAndView class is going to be used to render a Velocity template, and this template will have references to a ${weather} variable. The weather.vm template is stored in src/main/webapp/WEB-INF/vm and is shown in weather.vm Template Rendered by WeatherController.

In the WeatherController, before we render the output of the forecast, we pass the Weather object returned by the WeatherService to the save() method on WeatherDAO. Here we are saving this Weather object—using Hibernate—to an HSQLDB database. Later, in HistoryController, we will see how we can retrieve a history of weather forecasts that were saved by the WeatherController.

weather.vm Template Rendered by WeatherController. 

<b>Current Weather Conditions for:
${weather.location.city}, ${weather.location.region},
${weather.location.country}</b><br/>

<ul>
<li>Temperature: ${weather.condition.temp}</li>
<li>Condition: ${weather.condition.text}</li>
<li>Humidity: ${weather.atmosphere.humidity}</li>
<li>Wind Chill: ${weather.wind.chill}</li>
<li>Date: ${weather.date}</li>
</ul>

The syntax for this Velocity template is straightforward: variables are referenced using ${} notation. The expression between the curly braces references a property, or a property of a property on the weather variable, which was passed to this template by the WeatherController.

The HistoryController is used to retrieve recent forecasts that have been requested by the WeatherController. Whenever we retrieve a forecast from the WeatherController, that controller saves the Weather object to the database via the WeatherDAO. WeatherDAO then uses Hibernate to dissect the Weather object into a series of rows in a set of related database tables. The HistoryController is shown in simple-web HistoryController.

simple-web HistoryController. 

package org.sonatype.mavenbook.web;

import java.util.*;
import javax.servlet.http.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.sonatype.mavenbook.weather.model.*;
import org.sonatype.mavenbook.weather.persist.*;

public class HistoryController implements Controller {

    private LocationDAO locationDAO;
    private WeatherDAO weatherDAO;

    public ModelAndView handleRequest(HttpServletRequest request,
                                      HttpServletResponse response) throws Exception {
        String zip = request.getParameter("zip");
        Location location = locationDAO.findByZip(zip);
        List<Weather> weathers = weatherDAO.recentForLocation( location );

        Map<String,Object> model = new HashMap<String,Object>();
        model.put( "location", location );
        model.put( "weathers", weathers );

        return new ModelAndView("history", model);
    }

    public WeatherDAO getWeatherDAO() {
        return weatherDAO;
    }

    public void setWeatherDAO(WeatherDAO weatherDAO) {
        this.weatherDAO = weatherDAO;
    }

    public LocationDAO getLocationDAO() {
        return locationDAO;
    }

    public void setLocationDAO(LocationDAO locationDAO) {
        this.locationDAO = locationDAO;
    }
}

The HistoryController is wired to two DAO objects defined in simple-persist. The DAOs are bean properties of the HistoryController: WeatherDAO and LocationDAO. The goal of the HistoryController is to retrieve a List of Weather objects which correspond to the zip parameter. When the WeatherDAO saves the Weather object to the database, it doesn’t just store the zip code, it stores a Location object which is related to the Weather object in the simple-model. To retrieve a List of Weather objects, the HistoryController first retrieves the Location object that corresponds to the zip parameter. It does this by invoking the findByZip() method on LocationDAO.

Once the Location object has been retrieved, the HistoryController will then attempt to retrieve recent Weather objects that match the given Location. Once the List<Weather> has been retrieved, a HashMap is created to hold two variables for the history.vm Velocity template shown in history.vm Rendered by the HistoryController.

history.vm Rendered by the HistoryController. 

<b>
Weather History for: ${location.city}, ${location.region}, ${location.country}
</b>
<br/>

#foreach( $weather in $weathers )
<ul>
<li>Temperature: $weather.condition.temp</li>
<li>Condition: $weather.condition.text</li>
<li>Humidity: $weather.atmosphere.humidity</li>
<li>Wind Chill: $weather.wind.chill</li>
<li>Date: $weather.date</li>
</ul>
#end

The history.vm template in src/main/webapp/WEB-INF/vm references the location variable to print out information about the location of the forecasts retrieved from the WeatherDAO. This template then uses a Velocity control structure, #foreach, to loop through each element in the weathers variable. Each element in weathers is assigned to a variable named weather and the template between #foreach and #end is rendered for each forecast.

You’ve seen these Controller implementations, and you’ve seen that they reference other beans defined in simple-weather and simple-persist, they respond to HTTP requests, and they yield control to some mysterious templating system that knows how to render Velocity templates. All of this magic is configured in a Spring application context in src/main/webapp/WEB-INF/weather-servlet.xml. This XML configures the controllers and references other Spring-managed beans, it is loaded by a ServletContextListener which is also configured to load the applicationContext-weather.xml and applicationContext-persist.xml from the classpath. Let’s take a closer look at the weather-servlet.xml shown in Spring Controller Configuration weather-servlet.xml.

Spring Controller Configuration weather-servlet.xml. 

<beans>
    <bean id="weatherController" (1)
          class="org.sonatype.mavenbook.web.WeatherController">
        <property name="weatherService" ref="weatherService"/>
        <property name="weatherDAO" ref="weatherDAO"/>
    </bean>

    <bean id="historyController"
          class="org.sonatype.mavenbook.web.HistoryController">
        <property name="weatherDAO" ref="weatherDAO"/>
        <property name="locationDAO" ref="locationDAO"/>
    </bean>

    <!-- you can have more than one handler defined -->
    <bean id="urlMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="urlMap">
            <map>
                <entry key="/weather.x"> (2)
                        <ref bean="weatherController" />
                </entry>
                <entry key="/history.x">
                    <ref bean="historyController" />
                </entry>
            </map>
        </property>
    </bean>


    <bean id="velocityConfig" (3)
          class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/vm/"/>
    </bean>

    <bean id="viewResolver" (4)
          class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".vm"/>
        <property name="exposeSpringMacroHelpers" value="true"/>
    </bean>
</beans>

(1)

The weather-servlet.xml defines the two controllers as Spring-managed beans. weatherController has two properties which are references to weatherService and weatherDAO. historyController references the beans weatherDAO and locationDAO. When this ApplicationContext is created, it is created in an environment that has access to the ApplicationContext+s defined in both +simple-persist and simple-weather. In web.xml for simple-webapp you will see how Spring is configured to merge components from multiple Spring configuration files.

(2)

The urlMapping bean defines the URL patterns which invoke the WeatherController and the HistoryController. In this example, we are using the SimpleUrlHandlerMapping and mapping /weather.x to WeatherController and /history.x to HistoryController.

(3)

Since we are using the Velocity templating engine, we will need to pass in some configuration options. In the velocityConfig bean, we are telling Velocity to look for all templates in the /WEB-INF/vm directory.

(4)

Last, the viewResolver is configured with the class VelocityViewResolver. There are a number of ViewResolver implementations in Spring from a standard ViewResolver to render JSP or JSTL pages to a resolver which can render Freemarker templates. In this example, we’re configuring the Velocity templating engine and setting the default prefix and suffix which will be automatically appended to the names of the template passed to ModelAndView.

Finally, the simple-webapp project was a web.xml which provides the basic configuration for the web application. The web.xml file is shown in web.xml for simple-webapp.

web.xml for simple-webapp. 

<web-app id="simple-webapp" version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Simple Web Application</display-name>

    <context-param> (1)
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath:applicationContext-weather.xml
                classpath:applicationContext-persist.xml
            </param-value>
    </context-param>

    <context-param> (2)
            <param-name>log4jConfigLocation</param-name>
            <param-value>/WEB-INF/log4j.properties</param-value>
    </context-param>

    <listener> (3)
            <listener-class>
                org.springframework.web.util.Log4jConfigListener
            </listener-class>
    </listener>

    <listener>
        <listener-class> (4)
                org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet> (5)
            <servlet-name>weather</servlet-name>
            <servlet-class>
                org.springframework.web.servlet.DispatcherServlet
            </servlet-class>
            <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping> (6)
            <servlet-name>weather</servlet-name>
            <url-pattern>*.x</url-pattern>
    </servlet-mapping>
</web-app>

(1)

<calloutlist> Here’s a bit of magic which allows us to reuse the applicationContext-weather.xml and applicationContext-persist.xml in this project. The contextConfigLocation is used by the ContextLoaderListener to create an ApplicationContext. When the weather servlet is created, the weather-servlet.xml from Spring Controller Configuration weather-servlet.xml is going to be evaluated with the ApplicationContext created from this contextConfigLocation. In this way, you can define a set of beans in another project and you can reference these beans via the classpath. Since the simple-persist and simple-weather JARs are going to be in WEB-INF/lib, all we do is use the classpath: prefix to reference these files. (Another option would have been to copy these files to /WEB-INF and reference them with something like /WEB-INF/applicationContext-persist.xml).

(2)

The log4jConfigLocation is used to tell the Log4JConfigListener where to look for Log4J logging configuration. In this example, we tell Log4J to look in /WEB-INF/log4j.properties.

(3)

This makes sure that the Log4J system is configured when the web application starts. It is important to put this Log4JConfigListener before the+ ContextLoaderListener+; otherwise, you may miss important logging messages which point to a problem preventing application startup. If you have a particularly large set of beans managed by Spring, and one of them happens to blow up on application startup, your application will fail. If you have logging initialized before Spring starts, you might have a chance to catch a warning or an error. If you don’t have logging initialized before Spring starts up, you’ll have no idea why your application refuses to start.

(4)

The ContextLoaderListener is essentially the Spring container. When the application starts, this listener will build an ApplicationContext from the contextConfigLocation parameter.

(5)

We define a Spring MVC DispatcherServlet with a name of weather. This will cause Spring to look for a Spring configuration file in /WEB-INF/weather-servlet.xml. You can have as many DispatcherServlet+s as you need, a +DispatcherServlet can contain one or more Spring MVC Controller implementations.

(6)

All requests ending in .x will be routed to the weather servlet. Note that the .x extension has no particular meaning, it is an arbitrary choice and you can use whatever URL pattern you like.