Maven by Example

7.8. The Simple Command Module

The simple-command project is a command-line version of the simple-webapp. It is a utility that relies on the same dependencies: simple-persist and simple-weather. Instead of interacting with this application via a web browser, you would run the simple-command utility from the command-line.

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

Figure 7.4. Command Line Application Referencing simple-weather and simple-persist


POM for simple-command. 

<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-command</artifactId>
    <packaging>jar</packaging>
    <name>Simple Command Line Tool</name>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <testFailureIgnore>true</testFailureIgnore>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>hibernate3-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <components>
                        <component>
                            <name>hbm2ddl</name>
                            <implementation>annotationconfiguration</implementation>
                        </component>
                    </components>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>hsqldb</groupId>
                        <artifactId>hsqldb</artifactId>
                        <version>1.8.0.7</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <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>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
        </dependency>
    </dependencies>
</project>

This POM creates a JAR file which will contain the org.sonatype.mavenbook.weather.Main class shown in The Main Class for simple-command. In this POM we configure the Maven Assembly plugin to use a built-in assembly descriptor named jar-with-dependencies which creates a single JAR file containing all the bytecode a project needs to execute including the bytecode from the project you are building and all the bytecode from libraries your project depends upons.

The Main Class for simple-command. 

package org.sonatype.mavenbook.weather;

import java.util.List;

import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.sonatype.mavenbook.weather.model.Location;
import org.sonatype.mavenbook.weather.model.Weather;
import org.sonatype.mavenbook.weather.persist.LocationDAO;
import org.sonatype.mavenbook.weather.persist.WeatherDAO;

public class Main {

    private WeatherService weatherService;
    private WeatherDAO weatherDAO;
    private LocationDAO locationDAO;

    public static void main(String[] args) throws Exception {
        // Configure Log4J
        PropertyConfigurator.configure(Main.class.getClassLoader().getResource(
                                                                               "log4j.properties"));

        // Read the Zip Code from the Command-line (if none supplied, use 60202)
        String zipcode = "60202";
        try {
            zipcode = args[0];
        } catch (Exception e) {
        }

        // Read the Operation from the Command-line (if none supplied use weather)
        String operation = "weather";
        try {
            operation = args[1];
        } catch (Exception e) {
        }

        // Start the program
        Main main = new Main(zipcode);

        ApplicationContext context =
            new ClassPathXmlApplicationContext(
                                               new String[] { "classpath:applicationContext-weather.xml",
                                                              "classpath:applicationContext-persist.xml" });
        main.weatherService = (WeatherService) context.getBean("weatherService");
        main.locationDAO = (LocationDAO) context.getBean("locationDAO");
        main.weatherDAO = (WeatherDAO) context.getBean("weatherDAO");
        if( operation.equals("weather")) {
            main.getWeather();
        } else {
            main.getHistory();
        }
    }

    private String zip;

    public Main(String zip) {
        this.zip = zip;
    }

    public void getWeather() throws Exception {
        Weather weather = weatherService.retrieveForecast(zip);
        weatherDAO.save( weather );
        System.out.print(new WeatherFormatter().formatWeather(weather));
    }

    public void getHistory() throws Exception {
        Location location = locationDAO.findByZip(zip);
        List<Weather> weathers = weatherDAO.recentForLocation(location);
        System.out.print(new WeatherFormatter().formatHistory(location, weathers));
    }
}

The Main class has a reference to WeatherDAO, LocationDAO, and WeatherService. The static main() method in this class:

  • Reads the Zip Code from the first command line argument
  • Reads the Operation from the second command line argument. If the operation is "weather", the latest weather will be retrieved from the web service. If the operation is "history", the program will fetch historical weather records from the local database.
  • Loads a Spring ApplicationContext using two XML files loaded from simple-persist and simple-weather
  • Creates an instance of Main
  • Populates the weatherService, weatherDAO, and locationDAO with beans from the Spring ApplicationContext
  • Runs the appropriate method getWeather() or getHistory() depending on the specified operation.

In the web application we use Spring VelocityViewResolver to render a Velocity template. In the stand-alone implementation, we need to write a simple class which renders our weather data with a Velocity template. WeatherFormatter Renders Weather Data using a Velocity Template is a listing of the WeatherFormatter, a class with two methods that render the weather report and the weather history.

WeatherFormatter Renders Weather Data using a Velocity Template. 

package org.sonatype.mavenbook.weather;

import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import org.sonatype.mavenbook.weather.model.Location;
import org.sonatype.mavenbook.weather.model.Weather;

public class WeatherFormatter {

    private static Logger log = Logger.getLogger(WeatherFormatter.class);

    public String formatWeather( Weather weather ) throws Exception {
        log.info( "Formatting Weather Data" );
        Reader reader =
            new InputStreamReader( getClass().getClassLoader().
                                   getResourceAsStream("weather.vm"));
        VelocityContext context = new VelocityContext();
        context.put("weather", weather );
        StringWriter writer = new StringWriter();
        Velocity.evaluate(context, writer, "", reader);
        return writer.toString();
    }

    public String formatHistory( Location location, List<Weather> weathers )
        throws Exception {
        log.info( "Formatting History Data" );
        Reader reader =
            new InputStreamReader( getClass().getClassLoader().
                                   getResourceAsStream("history.vm"));
        VelocityContext context = new VelocityContext();
        context.put("location", location );
        context.put("weathers", weathers );
        StringWriter writer = new StringWriter();
        Velocity.evaluate(context, writer, "", reader);
        return writer.toString();
    }
}

The weather.vm template simply prints the zip code’s city, country, and region as well as the current temperature. The history.vm template prints the location and then iterates through the weather forecast records stored in the local database. Both of these templates are in ${basedir}/src/main/resources.

The weather.vm Velocity Template. 

****************************************
Current Weather Conditions for:
${weather.location.city},
${weather.location.region},
${weather.location.country}
****************************************

* Temperature: ${weather.condition.temp}
* Condition: ${weather.condition.text}
* Humidity: ${weather.atmosphere.humidity}
* Wind Chill: ${weather.wind.chill}
* Date: ${weather.date}

The history.vm Velocity Template. 

Weather History for:
${location.city},
${location.region},
${location.country}


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