Mapping Objects to Multiple XML Schemas – Weather Example
To make the example more “real”, the XML data will come from two different services that provide weather information: Google and Yahoo.
Java Model
The following domain model will be used for this post:
Weather Report
package blog.weather; import java.util.List; public class WeatherReport { private String location; private int currentTemperature; private String currentCondition; private List forecast; }
Forecast
package blog.weather; public class Forecast { private String dayOfTheWeek; private int low; private int high; private String condition; }
Google Weather API
First we will leverage Google’s Weather API. The following URL will be used to access the weather data for Ottawa, Canada:
http://www.google.com/ig/api?weather=Ottawa
The following is the result of performing the above query at time I was writing this article. I have highlighted the portions of the XML document that we will map to:
<xml_api_reply version="1"> <weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0"> <forecast_information> <city data="Ottawa, ON" /> <postal_code data="Ottawa" /> <latitude_e6 data="" /> <longitude_e6 data="" /> <forecast_date data="2011-09-08" /> <current_date_time data="2011-09-08 14:00:00 +0000" /> <unit_system data="US" /> </forecast_information> <current_conditions> <condition data="Mostly Cloudy" /> <temp_f data="66" /> <temp_c data="19" /> <humidity data="Humidity: 73%" /> <icon data="/ig/images/weather/mostly_cloudy.gif" /> <wind_condition data="Wind: NE at 13 mph" /> </current_conditions> <forecast_conditions> <day_of_week data="Thu" /> <low data="55" /> <high data="75" /> <icon data="/ig/images/weather/cloudy.gif" /> <condition data="Cloudy" /> </forecast_conditions> <forecast_conditions> <day_of_week data="Fri" /> <low data="46" /> <high data="77" /> <icon data="/ig/images/weather/mostly_sunny.gif" /> <condition data="Partly Sunny" /> </forecast_conditions> <forecast_conditions> <day_of_week data="Sat" /> <low data="43" /> <high data="68" /> <icon data="/ig/images/weather/sunny.gif" /> <condition data="Clear" /> </forecast_conditions> <forecast_conditions> <day_of_week data="Sun" /> <low data="55" /> <high data="75" /> <icon data="/ig/images/weather/sunny.gif" /> <condition data="Clear" /> </forecast_conditions> </weather> </xml_api_reply>
Java Model – Mapped to Google’s XML Schema via Annotations
We will map the result of the Google weather API via a combination of standard JAXB and MOXy extension annotations.
Weather Report
package blog.weather; import java.util.List; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="xml_api_reply") @XmlType(propOrder={"location", "currentCondition", "currentTemperature", "forecast"}) @XmlAccessorType(XmlAccessType.FIELD) public class WeatherReport { @XmlPath("weather/forecast_information/city/@data") private String location; @XmlPath("weather/current_conditions/temp_f/@data") private int currentTemperature; @XmlPath("weather/current_conditions/condition/@data") private String currentCondition; @XmlPath("weather/forecast_conditions") private List<Forecast> forecast; }
Forecast
package blog.weather; import org.eclipse.persistence.oxm.annotations.XmlPath; public class Forecast { @XmlPath("day_of_week/@data") private String dayOfTheWeek; @XmlPath("low/@data") private int low; @XmlPath("high/@data") private int high; @XmlPath("condition/@data") private String condition; }
Demo
The following demo code will read the XML data for Google’s weather service, and marshal the objects back to XML:
package blog.weather; import java.net.URL; import javax.xml.bind.*; public class GoogleDemo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(WeatherReport.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); URL url = new URL("http://www.google.com/ig/api?weather=Ottawa"); WeatherReport weatherReport = (WeatherReport) unmarshaller.unmarshal(url); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(weatherReport, System.out); } }
Output
Below is the result of running the demo code. The output represents the portion of the XML document that we had mapped to:
<?xml version="1.0" encoding="UTF-8"?> <xml_api_reply> <weather> <forecast_information> <city data="Ottawa, ON"/> </forecast_information> <current_conditions> <condition data="Mostly Cloudy"/> <temp_f data="68"/> </current_conditions> <forecast_conditions> <day_of_week data="Thu"/> <low data="55"/> <high data="75"/> <condition data="Cloudy"/> </forecast_conditions> <forecast_conditions> <day_of_week data="Fri"/> <low data="46"/> <high data="77"/> <condition data="Partly Sunny"/> </forecast_conditions> <forecast_conditions> <day_of_week data="Sat"/> <low data="43"/> <high data="68"/> <condition data="Clear"/> </forecast_conditions> <forecast_conditions> <day_of_week data="Sun"/> <low data="55"/> <high data="75"/> <condition data="Clear"/> </forecast_conditions> </weather> </xml_api_reply>
Yahoo Weather API
The following URL will be used to access the weather data for Ottawa using the Yahoo Weather API (3369 is the WOEID for Ottawa):
http://weather.yahooapis.com/forecastrss?w=3369
The following is the result of performing the above query at time I was writing this article:
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"> <channel> <title>Yahoo! Weather - Ottawa, CA</title> <link>http://us.rd.yahoo.com/dailynews/rss/weather/Ottawa__CA/*http://weather.yahoo.com/forecast/CAXX0343_f.html</link> <description>Yahoo! Weather for Ottawa, CA</description> <language>en-us</language> <lastBuildDate>Thu, 08 Sep 2011 10:58 am EDT</lastBuildDate> <ttl>60</ttl> <yweather:location city="Ottawa" region="ON" country="Canada" /> <yweather:units temperature="F" distance="mi" pressure="in" speed="mph" /> <yweather:wind chill="66" direction="40" speed="12" /> <yweather:atmosphere humidity="73" visibility="" pressure="30.14" rising="0" /> <yweather:astronomy sunrise="6:31 am" sunset="7:25 pm" /> <image> <title>Yahoo! Weather</title> <width>142</width> <height>18</height> <link>http://weather.yahoo.com</link> <url>http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif</url> </image> <item> <title>Conditions for Ottawa, CA at 10:58 am EDT</title> <geo:lat>45.42</geo:lat> <geo:long>-75.69</geo:long> <link>http://us.rd.yahoo.com/dailynews/rss/weather/Ottawa__CA/*http://weather.yahoo.com/forecast/CAXX0343_f.html</link> <pubDate>Thu, 08 Sep 2011 10:58 am EDT</pubDate> <yweather:condition text="Mostly Cloudy" code="28" temp="66" date="Thu, 08 Sep 2011 10:58 am EDT" /> <description><![CDATA[ <img src="http://l.yimg.com/a/i/us/we/52/28.gif"/> <b>Current Conditions:</b> Mostly Cloudy, 66 F <b>Forecast:</b> Thu - Partly Cloudy. High: 75 Low: 57 Fri - Partly Cloudy. High: 79 Low: 53 <a href="http://us.rd.yahoo.com/dailynews/rss/weather/Ottawa__CA/*http://weather.yahoo.com/forecast/CAXX0343_f.html">Full Forecast at Yahoo! Weather</a> (provided by <a href="http://www.weather.com" >The Weather Channel</a>) ]]></description> <yweather:forecast day="Thu" date="8 Sep 2011" low="57" high="75" text="Partly Cloudy" code="30" /> <yweather:forecast day="Fri" date="9 Sep 2011" low="53" high="79" text="Partly Cloudy" code="30" /> <guid isPermaLink="false">CAXX0343_2011_09_09_7_00_EDT</guid> </item> </channel> </rss><!-- api4.weather.sp2.yahoo.com uncompressed/chunked Thu Sep 8 08:32:54 PDT 2011 -->
Java Model – Mapped to Yahoo’s XML Schema via XML Metadata
Since we can not supply a second set of mappings to an object model via annotations, we must supply subsequent mappings by leveraging MOXy’s XML metadata. By default MOXy’s mapping document is used to supplement any annotations that are specified on the model. However, if the xml-mapping-metadata-complete flag is set, then the XML metadata will completely replace the metadata provided by annotations (the annotations for the Google mapping will remain on the POJOs, but the xml-mapping-metadata-complete flag tells MOXy to ignore them).
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="blog.weather" xml-mapping-metadata-complete="true"> <xml-schema element-form-default="QUALIFIED"> <xml-ns prefix="yweather" namespace-uri="http://xml.weather.yahoo.com/ns/rss/1.0"/> </xml-schema> <java-types> <java-type name="WeatherReport" xml-accessor-type="FIELD"> <xml-root-element name="rss"/> <xml-type prop-order="location currentTemperature currentCondition forecast"/> <java-attributes> <xml-attribute java-attribute="location" xml-path="channel/yweather:location/@city"/> <xml-attribute java-attribute="currentTemperature" name="channel/item/yweather:condition/@temp"/> <xml-attribute java-attribute="currentCondition" name="channel/item/yweather:condition/@text"/> <xml-element java-attribute="forecast" name="channel/item/yweather:forecast"/> </java-attributes> </java-type> <java-type name="Forecast" xml-accessor-type="FIELD"> <java-attributes> <xml-attribute java-attribute="dayOfTheWeek" name="day"/> <xml-attribute java-attribute="low"/> <xml-attribute java-attribute="high"/> <xml-attribute java-attribute="condition" name="text"/> </java-attributes> </java-type> </java-types> </xml-bindings>
Demo
The following demo code will read the XML data for Yahoo’s weather service, and marshal the objects back to XML. Due to a MOXy bug regarding unmapped CDATA sections (https://bugs.eclipse.org/357145), a filtered XMLStreamReader was used to remove it from the XML input:
package blog.weather; import java.util.HashMap; import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.stream.StreamFilter; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; import javax.xml.transform.stream.StreamSource; import org.eclipse.persistence.jaxb.JAXBContextFactory; public class YahooDemo { public static void main(String[] args) throws Exception { Map<String, Object> properties = new HashMap<String, Object>(1); properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "blog/weather/yahoo-binding.xml"); JAXBContext jc = JAXBContext.newInstance(new Class[] {WeatherReport.class}, properties); XMLInputFactory xif = XMLInputFactory.newFactory(); StreamSource xml = new StreamSource("http://weather.yahooapis.com/forecastrss?w=3369"); XMLStreamReader xsr = xif.createXMLStreamReader(xml); xsr = xif.createFilteredReader(xsr, new CDATAFilter()); Unmarshaller unmarshaller = jc.createUnmarshaller(); WeatherReport weatherReport = (WeatherReport) unmarshaller.unmarshal(xsr); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(weatherReport, System.out); } private static class CDATAFilter implements StreamFilter { public boolean accept(XMLStreamReader xsr) { return XMLStreamReader.CDATA != xsr.getEventType(); } } }
Output
Below is the result of running the demo code. The output represents the portion of the XML document that we had mapped to:
<?xml version="1.0" encoding="UTF-8"?> <rss xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"> <channel> <yweather:location city="Ottawa"/> <item> <yweather:forecast day="Thu" low="57" high="74" text="Partly Cloudy"/> <yweather:forecast day="Fri" low="53" high="79" text="Partly Cloudy"/> </item> </channel> </rss>
Reference: Mapping Objects to Multiple XML Schemas – Weather Example from our JCG partner Blaise Doughan at the Java XML & JSON Binding blog.
Related Articles :