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
Mainclass contains a staticmain()function: the entry point for this system. -
org.sonatype.mavenbook.weather.Weather -
The
Weatherclass 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
YahooRetrieverclass connects to Yahoo! Weather and returns anInputStreamof the data from the feed. -
org.sonatype.mavenbook.weather.YahooParser -
The
YahooParserclass parses the XML from Yahoo! Weather, and returns aWeatherobject. -
org.sonatype.mavenbook.weather.WeatherFormatter -
The
WeatherFormatterclass takes aWeatherobject, creates aVelocityContext, 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.
