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:
<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
<?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):
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"/><br /> <b>Current Conditions:</b><br /> Mostly Cloudy, 66 F<BR /> <BR /><b>Forecast:</b><BR /> Thu - Partly Cloudy. High: 75 Low: 57<br /> Fri - Partly Cloudy. High: 79 Low: 53<br /> <br /> <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><BR/><BR/> (provided by <a href="http://www.weather.com" >The Weather Channel</a>)<br/> ]]></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
<?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>
Further Reading
If you enjoyed this post, then you may also be interested in:
Hello,
ReplyDeleteI have a quite big object model, already annotated. I need to be able to work with multiple versions of the same schema. There aren't many significant differences between schema versions, but each of these schemas is in a separate namespace. Do I have to create for each schema version complete bindings which will totally override the annotations? Or maybe there is a way to override just the mappings that change between versions? Is there a way to automatically generate bindings from a an annotated object model?
If you do not specify xml-mapping-metadata-complete="true" on the xml-bindings element, then the metadata supplied via the mapping document will be used to augment rather than replace the existing metadata. You could leverage this to just tweak the namespace.
DeleteHi,
ReplyDeleteI am trying to implement the same scenario in my project. This worked fine when I had a small project and was able to map the bindings. But when I moved this logic to my existing mavenized web project, I keep getting the error
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:273)
at com.tagcmd.api.core.BaseTest.executeMethod(BaseTest.java:30)
at com.tagcmd.api.mrm.task.dao.impl.TaskDaoTest.task_rag_xml_to_proc_should_be_valid(TaskDaoTest.java:209)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: javax.xml.bind.JAXBException: property "eclipselink-oxm-xml" is not supported
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:130)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:363)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at com.tagcmd.api.mrm.task.dao.impl.TaskDao.convertTaskStatisticsToXMLString(TaskDao.java:723)
... 31 more
Though I have jaxb.properties in the same package as my domain object. I have also placed the file in the package where the TaskDao.java is present.
My code:
TaskDao.java
....
Map properties = new HashMap(1);
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "src/main/java/com/tagcmd/domain/MyTask.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] { Task.class }, properties);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
You need to make sure that you put the jaxb.properties file under the resources and not the java directory. Here is a link to an example:
Delete- https://github.com/bdoughan/blog20110322/tree/master/src/main/resources/blog/predicate