Over a million developers have joined DZone.

Mapping Objects to Multiple XML Schemas - Weather Example

· Java Zone

Navigate the Maze of the End-User Experience and pick up this APM Essential guide, brought to you in partnership with CA Technologies

I have written previous posts on EclipseLink JAXB (MOXy)'s @XmlPath and external binding file extensions.  In this post I will demonstrate how powerful these extensions are by mapping a single object model to two different XML schemas.  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;
 
}

 

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:


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;
 
}

Specify MOXy as the JAXB Provider (jaxb.properties)

To configure MOXy as your JAXB provider simply add a file named jaxb.properties in the same package as your domain model with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

For more information see:  Specifying EclipseLink MOXy as Your JAXB Provider.

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:


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):


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, this bug has been fixed in EclipseLink 2.3.1), 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>

 

From http://blog.bdoughan.com/2011/09/mapping-objects-to-multiple-xml-schemas.html

Thrive in the application economy with an APM model that is strategic. Be E.P.I.C. with CA APM.  Brought to you in partnership with CA Technologies.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}