Maven by Example

4.6. Simple Weather Source Code

The Simple Weather command-line application consists of five Java classes.

org.sonatype.mavenbook.weather.Main
The Main class contains a static main() function: the entry point for this system.
org.sonatype.mavenbook.weather.Weather
The Weather class is a straightforward Java bean that holds the location of our weather report and some key facts, such as the temperature and humidity.
org.sonatype.mavenbook.weather.YahooRetriever
The YahooRetriever class connects to Yahoo! Weather and returns an InputStream of the data from the feed.
org.sonatype.mavenbook.weather.YahooParser
The YahooParser class parses the XML from Yahoo! Weather, and returns a Weather object.
org.sonatype.mavenbook.weather.WeatherFormatter
The WeatherFormatter class takes a Weather object, creates a VelocityContext, and evaluates a Velocity template.

Although we won’t dwell on the code here, we will provide all the necessary code for you to get the example working. We assume that most readers have downloaded the examples that accompany this book, but we’re also mindful of those who may wish to follow the example in this chapter step-by-step. The sections that follow list classes in the simple-weather project. Each of these classes should be placed in the same package: org.sonatype.mavenbook.weather.

Let’s remove the App and the AppTest classes created by archetype:generate and add our new package. In a Maven project, all of a project’s source code is stored in src/main/java. From the base directory of the new project, execute the following commands:

$ cd src/test/java/org/sonatype/mavenbook
$ rm AppTest.java
$ cd ../../../../../..
$ cd src/main/java/org/sonatype/mavenbook
$ rm App.java
$ mkdir weather
$ cd weather

This creates a new package named org.sonatype.mavenbook.weather. Now we need to put some classes in this directory. Using your favorite text editor, create a new file named Weather.java with the contents shown in Simple Weather’s Weather Model Object.

Simple Weather’s Weather Model Object. 

package org.sonatype.mavenbook.weather;

public class Weather {
    private String city;
    private String region;
    private String country;
    private String condition;
    private String temp;
    private String chill;
    private String humidity;

    public Weather() {}

    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }

    public String getRegion() { return region; }
    public void setRegion(String region) { this.region = region; }

    public String getCountry() { return country; }
    public void setCountry(String country) { this.country = country; }

    public String getCondition() { return condition; }
    public void setCondition(String condition) { this.condition = condition; }

    public String getTemp() { return temp; }
    public void setTemp(String temp) { this.temp = temp; }

    public String getChill() { return chill; }
    public void setChill(String chill) { this.chill = chill; }

    public String getHumidity() { return humidity; }
    public void setHumidity(String humidity) { this.humidity = humidity; }
}

The Weather class defines a simple bean that is used to hold the weather information parsed from the Yahoo! Weather feed. This feed provides a wealth of information, from the sunrise and sunset times to the speed and direction of the wind. To keep this example as simple as possible, the Weather model object keeps track of only the temperature, chill, humidity, and a textual description of current conditions.

Now, in the same directory, create a file named Main.java. This Main class will hold the static main() function—the entry point for this example.

Simple Weather’s Main Class. 

package org.sonatype.mavenbook.weather;

import java.io.InputStream;

import org.apache.log4j.PropertyConfigurator;


public class Main {

    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 ) {}

        // Start the program
        new Main(zipcode).start();
    }

    private String zip;

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

    public void start() throws Exception {
        // Retrieve Data
        InputStream dataIn = new YahooRetriever().retrieve( zip );

        // Parse Data
        Weather weather = new YahooParser().parse( dataIn );

        // Format (Print) Data
        System.out.print( new WeatherFormatter().format( weather ) );
    }
}

The main() function shown above configures Log4J by retrieving a resource from the classpath, it then tries to read a zip code from the command-line. If an exception is thrown while it is trying to read the zip code, the program will default to a zip code of 60202. Once it has a zip code, it instantiates an instance of Main and calls the start() method on an instance of Main. The start() method calls out to the YahooRetriever to retrieve the weather XML. The YahooRetriever returns an InputStream which is then passed to the YahooParser. The YahooParser parses the Yahoo! Weather XML and returns a Weather object. Finally, the+ WeatherFormatter+ takes a Weather object and spits out a formatted String which is printed to standard output.

Create a file named YahooRetriever.java in the same directory with the contents shown in Simple Weather’s YahooRetriever Class.

Simple Weather’s YahooRetriever Class. 

package org.sonatype.mavenbook.weather;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import org.apache.log4j.Logger;

public class YahooRetriever {

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

    public InputStream retrieve(String zipcode) throws Exception {
        log.info( "Retrieving Weather Data" );
        String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode;
        URLConnection conn = new URL(url).openConnection();
        return conn.getInputStream();
    }
}

This simple class opens a URLConnection to the Yahoo! Weather API and returns an InputStream. To create something to parse this feed, we’ll need to create the YahooParser.java file in the same directory.

Simple Weather’s YahooParser Class. 

package org.sonatype.mavenbook.weather;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.io.SAXReader;

public class YahooParser {

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

    public Weather parse(InputStream inputStream) throws Exception {
        Weather weather = new Weather();

        log.info( "Creating XML Reader" );
        SAXReader xmlReader = createXmlReader();
        Document doc = xmlReader.read( inputStream );

        log.info( "Parsing XML Response" );
        weather.setCity( doc.valueOf("/rss/channel/y:location/@city") );
        weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") );
        weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") );
        weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") );
        weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") );
        weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") );
        weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") );

        return weather;
    }

    private SAXReader createXmlReader() {
        Map<String,String> uris = new HashMap<String,String>();
        uris.put( "y", "http://xml.weather.yahoo.com/ns/rss/1.0" );

        DocumentFactory factory = new DocumentFactory();
        factory.setXPathNamespaceURIs( uris );

        SAXReader xmlReader = new SAXReader();
        xmlReader.setDocumentFactory( factory );
        return xmlReader;
    }
}

The YahooParser is the most complex class in this example. We’re not going to dive into the details of Dom4J or Jaxen here, but the class deserves some explanation. YahooParser's parse() method takes an InputStream and returns a Weather object. To do this, it needs to parse an XML document with Dom4J. Since we’re interested in elements under the Yahoo! Weather XML namespace, we need to create a namespace-aware SAXReader in the createXmlReader() method. Once we create this reader and parse the document, we get an org.dom4j.Document object back. Instead of iterating through child elements, we simply address each piece of information we need using an XPath expression. Dom4J provides the XML parsing in this example, and Jaxen provides the XPath capabilities.

Once we’ve created a Weather object, we need to format our output for human consumption. Create a file named WeatherFormatter.java in the same directory as the other classes.

Simple Weather’s WeatherFormatter Class. 

package org.sonatype.mavenbook.weather;

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

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

public class WeatherFormatter {

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

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

The WeatherFormatter uses Velocity to render a template. The format() method takes a Weather bean and spits out a formatted String. The first thing the format() method does is load a Velocity template from the classpath named output.vm. We then create a VelocityContext which is populated with a single Weather object named weather. A StringWriter is created to hold the results of the template merge. The template is evaluated with a call to Velocity.evaluate() and the results are returned as a String.

Before we can run this example, we’ll need to add some resources to our classpath.