Showing posts with label jaxb. Show all posts
Showing posts with label jaxb. Show all posts

Friday, May 8, 2009

JAXB xjb & too many nodes

As I went into a while ago, you can use xjb to customize JAXB bindings. Today I learned of a tragic shortcoming. So say you have this snippet
<jaxb:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
 version="1.0">
 <jaxb:bindings schemaLocation="train.xsd" node="//xsd:element[@name='car']">
  <jaxb:property name="cars" />
 </jaxb:bindings>
</jaxb:bindings>
In that example there was a complexType with the name train, which contained an element with the name car which corresponded to the complexType car. All is right in the world. But what if there was another element somewhere, probably in some other complexType that contained a car? Well, that'd be kosher unless you were trying to customize the bindings with the above snippet. Instead you'll end up seeing
[ERROR] Error while parsing schema(s).Location [ file:/C:/Dev/Workspace_Netbeans /my-project/src/main/resources/train.xjb{5,80}].com.sun.istack.SAXParseException2: XPath evaluation of "//xsd:element[@name='widget']" results in too many (2) target nodes
There was an enhancement request put in years ago for XPath to be able to match multiple nodes, but like Bill O'Reilly releasing a softcore romance novel I wouldn't hold my breathe. Oh wait.

Thursday, April 9, 2009

Reading in XML with JAXB

A bit ago I covered dynamic class creation with xjc and maven. Now for the kinda-second-part-but-doesn't-really-have-to-be, how to read in an xml file into your beans.

So based off of the xsd file I created in the aforementioned, here is my xml file representing a train. Cho chooo mother fucker.
<?xml version="1.0" encoding="UTF-8"?>
<train xmlns="http://www.noviidesign.com/xjcexample/train" name="Pepper Jack" id="pepper_jack" color="gray">
  <car name="Car 1" id="car1" maxPassengers="50" />
  <car name="Car 2" id="car2" maxPassengers="75" />
  <car name="Car 3" id="car3" maxPassengers="75" />
</train>
Pretty simple. If you wanted you could have multiple trains within one file, or multiple files with one train in each and just add some looping to your code. Once again I assume you just have this xml files in your src/main/resources folder.

And here is the code. Not much to it.
InputStream xmlFile = this.getClass().getResourceAsStream("/trains/pepperjack.xml");

JAXBContext jc;
try {
  jc = JAXBContext.newInstance("com.noviidesign.xjcexample");
  Unmarshaller u = jc.createUnmarshaller();
  JAXBElement<train> root = (JAXBElement<train>) u.unmarshal(xmlFile);
  Train train = root.getValue();

  System.out.println("Train id: " + train.getId());
  System.out.println("Train name: " + train.getName());
  System.out.println("Train color: " + train.getColor());

  for(Car car : train.getCars()){
      System.out.println("Car id: " + car.getId());
      System.out.println("Car name: " + car.getName());
      System.out.println("Car max passangers: " + car.getMaxPassengers());
  }
} catch (JAXBException ex) {
  logger.error("Exception in jaxb parsing:", ex);
}
The only thing to be aware of here is the line where you get the new JAXBContext instance. I pass it the context path (where you'll find the Train and Car classes), but you can also pass it an array of classes, a classloader, or combination of them.

And this is all you'll need in your pom. (you will be using maven...)
<dependencies>
   <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.0</version>
   </dependency>
</dependencies>
<plugin>
Thats it. Simple like your mom.

Friday, March 27, 2009

JAXB xjc & maven2

I'm a fan of the Metro Web Services products (JAX-WS and JAXB). Not a fanboy, but a fan. I am a fanboy of maven though, and if you're not you just suck. Its the sauce that makes the world go round. That said, more often than not when using an xml to java compiler you don't have a need to have it automated. An ant task would suffice. And there are a bunch of great resources out there on that. But sometimes you want/need new classes based on an ever changed xml schema. WOOT MAVEN!

So first you'll need the plugin. Unfortunately as of now its not in a maven repo (which I think is mostly due to the minimal need for maven automation), but you can download the plugin here and add it to your local repo or local/companies repository.

Now you need some stchuff in your pom.
<plugin>
<groupId>com.sun.tools.xjc.maven2</groupId>
<artifactId>maven-jaxb-plugin</artifactId>
<version>1.1</version>
<executions>
   <execution>
       <goals>
           <goal>generate</goal>
       </goals>
   </execution>
</executions>
<configuration>
   <generatePackage>com.noviidesign.xjcexample</generatePackage>
   <includeSchemas>
       <includeSchema>**/*.xsd</includeSchema>
   </includeSchemas>
   <includeBindings>
       <includeBinding>*.xjb</includeBinding>
   </includeBindings>
   <strict>true</strict>
   <verbose>true</verbose>
   <removeOldOutput>true</removeOldOutput>
</configuration>
</plugin>
This will remove your old classes and creating code whenever you run a maven goal.

Now when you run this you should have your *.xsd schema in your /resources folder (for a webapp). I also have an *.xjb file in the plugin configuration above. First, lets look at what an xsd schema would look like that'd need the xjb file.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.noviidesign.com/xjcexample/train" xmlns:tns="http://www.noviidesign.com/xjcexample/train" elementFormDefault="qualified">
 <element name="train" type="tns:train" />
 <complexType name="train">
  <sequence>
   <element name="car" type="tns:car" minOccurs="1" maxOccurs="unbounded" />
  </sequence>
  <attribute type="string" name="name" use="required" />
  <attribute type="string" name="id" use="required" />
  <attribute type="string" name="color" use="required" />
 </complexType>
 <complexType name="car">
  <attribute type="string" name="name" use="required" />
  <attribute type="string" name="id" use="required" />
  <attribute type="string" name="maxPassengers" use="required" />
 </complexType>
</schema>
Now this schema is just for example purposes but should get the point across. If needed you can find more information than you could care about on xml schemas on le internet. So, now when the classes are created you'll have the following
package com.noviidesign.xjcexample;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "train", propOrder = {"car"} )
public class Train {

 @XmlElement(required = true)
 protected List car;
 @XmlAttribute(required = true)
 protected String color;
 @XmlAttribute(required = true)
 protected String id;
 @XmlAttribute(required = true)
 protected String name;
 
 public List getCar() {
  if (car == null) {
   car = new ArrayList();
  }
  return this.car;
 }
 
 public String getColor() {
  return color;
 }
 
 public void setColor(String value) {
  this.color = value;
 }
 
 public String getId() {
  return id;
 }
 
 public void setId(String value) {
  this.id = value;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String value) {
  this.name = value;
 }
}

package com.noviidesign.xjcexample;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "car")

public class Car {

 @XmlAttribute(required = true)
 protected String id;
 @XmlAttribute(required = true)
 protected String maxPassengers;
 @XmlAttribute(required = true)
 protected String name;
 
 public String getId() {
  return id;
 }
 
 public void setId(String value) {
  this.id = value;
 }
 
 public String getMaxPassengers() {
  return maxPassengers;
 }
 public void setMaxPassengers(String value) {
  this.maxPassengers = value;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String value) {
  this.name = value;
 }
}
So nothing mind blowing, just some regular ol' classes w/some xml annotation. But what I don't like is in Train.java we have protected List car; and subsequently public List getCar(); I'd much rather have List cars; and public List getCars(); And this is where our *.xjb file comes in. It'd look as follows
<jaxb:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
 version="1.0">
 <jaxb:bindings schemaLocation="train.xsd" node="//xsd:element[@name='car']">
  <jaxb:property name="cars" />
 </jaxb:bindings>
</jaxb:bindings>
And thats really it. Now again you'd usually do this with an ant task, but if you have a need for maven hopefully this will help get you going in the right direction.

In a bit I'll cover how to read in xml into your classes you created.