Mapping Objects to Multiple XML Schemas - Weather Example
Join the DZone community and get the full member experience.
Join For FreeI 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
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:
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
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:
Java Model - Mapped to Yahoo's XML Schema via XML Metadata
<?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
XML
Object (computer science)
Schema
Opinions expressed by DZone contributors are their own.
Comments