티스토리 뷰

Computer/XML

[JAXB]

인생이글케쉬우냐 2009. 11. 13. 15:54
[참조] http://cafe.naver.com/sojw.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=4385
[참조-한글] http://blog.naver.com/mirnae?Redirect=Log&logNo=100054958660
http://blogs.sun.com/CoreJavaTechTips/entry/exchanging_data_with_xml_and


Exchanging Data with XML and JAXB, Part 1
by Jennie Hall

In this tip, you'll learn how to to use the Java Architecture for XML Binding (JAXB) in Java SE 6to exchange XML data between systems without having to delve into thespecifics of XML processing. Among other key improvements, JAXB 2.0offers support for binding Java objects to XML via the use ofannotations. This feature allows you to generate XML data from yourapplication simply by annotating your existing object model.

What's JAXB?

JAXB simplifies the use of XML data in Java applications by shieldingyou and your code from the low-level details of working with XML.Essentially, JAXB allows you to move easily back-and-forth between XMLand Java. A JAXB implementation supplies a schema compiler, which takesin an XML schema and generates Java classes which map to that schema.Data from XML documents which are instances of the schema can be boundautomatically to the generated classes by JAXB's binding runtimeframework. This is called unmarshalling. Once unmarshalled, content canbe manipulated or modified in Java as needed. JAXB can also write(marshal) data from Java objects to an XML instance document. JAXBoptionally performs validation of content as part of these operations.

Inaddition to the schema compiler, a JAXB implementation also provides aschema generator, which looks at annotations on Java classes andgenerates a corresponding XML schema. This feature is new to JAXB 2.0.

The Sample Application

A veterinary office wants to send notices to its clients reminding themof their pets' upcoming appointments. Because they are nice folks, theyalso like to send a birthday card to an owner on the pet's birthday.The veterinary office, NiceVet, contracts with a service provider,WePrintStuff, to do their printing and mailing for them.

NiceVet already has an application which maintainsinformation about the animals under NiceVet's care and their owners.Here's a diagram of NiceVet's existing object model:


NiceVetneeds a way to get the necessary notification data to WePrintStuff forprocessing without spending a lot of time and money modifying theircurrent application.

Generating the XML

It'stime to annotate our object model so we can generate some XML data forthe printing and mailing service. Looking at NiceVet's object model, wesee that the PrintOrder class holds the information aboutupcoming events, and thus makes sense as the root element of the datawe want to send. Let's add the @XmlRootElement annotation to the PrintOrder class. This will establish printOrder as the root element of our generated XML.

<pre> import javax.xml.bind.annotation.XmlRootElement;

// the root element of our generated XML
@XmlRootElement(name = "printOrder")
public class PrintOrder {

private long id;
private List<Event> events;</pre>

Let's add some more annotations:

<pre> import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

// the root element of our generated XML
@XmlRootElement(name = "printOrder")
// maps this class to a generated schema type
@XmlType(name = "PrintOrderType", propOrder = {"events"})
// bind all non-static, non-transient fields
// to XML unless annotated with @XmlTransient
@XmlAccessorType(XmlAccessType.FIELD)
public class PrintOrder {

// maps this field to an XML attribute
@XmlAttribute(required = true)
private long id;
// custom adapter maps this field to a type,
// NotificationsType, which makes it easy to
// generate the desired XML
@XmlJavaTypeAdapter(value=EventAdapter.class)
@XmlElement(name = "notifications")
private List<Event> events;</pre>

@XmlType maps the PrintOrder class to a generated schema type named PrintOrderType. The @XmlAccessorType(XmlAccessType.FIELD) annotation tells JAXB to bind all non-static, non-transient fields to XML unless annotated with @XmlTransient, which prevents the mapping of a field or property. With the XmlAccessType.FIELD setting, getter/setter pairs are not bound to XML unless explicitly annotated. The default setting is XmlAccessType.PUBLIC_MEMBER (all public fields and getter/setter pairs are bound unless otherwise annotated); other settings are XmlAccessType.PROPERTY and XmlAccessType.NONE.

Moving down the code listing, we see that the field id maps to an XML attribute on the PrintOrderType element. Some kind of identifier on the print order is required, and we've annotated the id field accordingly.

The PrintOrder class has a list of events which contains both appointments and birthdays. The annotation @XmlJavaTypeAdapter tells JAXB that we are going to use a custom adapter, EventAdapter, to help us map this Java construct to XML. To support this annotation, we need to write an adapter class which extends XmlAdapter and overrides its abstract marshal and unmarshal methods.

<pre> public class EventAdapter extends XmlAdapter<NotificationsType, List<Event>> {

// adapt original Java construct to a type, NotificationsType,
// which we can easily map to the XML output we want
public NotificationsType marshal(List<Event> events) throws Exception {
List<Appointment> appointments = new ArrayList<Appointment>();
List<Birthday> birthdays = new ArrayList<Birthday>();

for (Event e : events) {
if (e instanceof Appointment) {
appointments.add((Appointment)e);
} else {
birthdays.add((Birthday)e);
}
}
return new NotificationsType(appointments, birthdays);
}

// reverse operation: map XML type to Java
public List<Event> unmarshal(NotificationsType notifications) throws Exception { ... }
</pre>

EventAdapter's marshal method takes the original Java construct, List<Event> events, and converts it to a type which maps more easily to the XML output we want. Let's take a look at NotificationsType:

<pre> import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElement;
import java.util.List;

public class NotificationsType {

// produce a wrapper XML element around this collection
@XmlElementWrapper(name = "appointments")
// maps each member of this list to an XML element named appointment
@XmlElement(name = "appointment")
//private List<Appointment> appointments;
private List<Appointment> appointments;
// produce a wrapper XML element around this collection
@XmlElementWrapper(name = "birthdays")
// maps each member of this list to an XML element named birthday
@XmlElement(name = "birthday")
//private List<Birthday> birthdays;
private List<Birthday> birthdays;

public NotificationsType() {}

public NotificationsType(List<Appointment> appointments, List<Birthday> birthdays) {
this.appointments = appointments;
this.birthdays = birthdays;
}
</pre>

The @XmlElementWrapper annotation creates a wrapper XML element around each collection, and the @XmlElement annotation maps each member of the collection to an XML element of the appropriate type. Combined with the @XmlElement(name = "notifications") annotation on PrintOrder.events, what we end up with will look something like this:

<pre> <printOrder>
<notifications>
<appointments>
<appointment>
...
</appointment>
</appointments>
<birthdays>
<birthday>
...
</birthday>
</birthdays>
</notifications>
</printOrder>
</pre>

Another annotation worth mentioning lies in the abstractbase class Event (listing below), from which subclasses Appointment andBirthday inherit. This inheritance hierarchy won't hold much meaningfor our service provider, WePrintStuff, so we'll eliminate it with the @XmlTransient annotation on Event. We still need fields owner and pet, however, so we annotate them with @XmlElement(required = true).

<pre> import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;

// no need to capture this inheritance hierarchy in XML
@XmlTransient
public abstract class Event {

@XmlElement(required = true)
private Owner owner;
@XmlElement(required = true)
private Pet pet;

public Event() {}

public Event(Owner owner, Pet pet) {
this.owner = owner;
this.pet = pet;
}
</pre>

When objects of type Birthday and Appointment are marshalled to XML,they will include all the relevant data from their supertype, Event. Take a look at the listing below for the Birthday class:

<pre> @XmlType(name = "BirthdayType", propOrder = {"age", "birthday", "owner", "pet"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Birthday extends Event {

@XmlElement(required = true)
private int age;

public Birthday() {}

public Birthday(Owner owner, Pet pet) {
super(owner, pet);
this.age = this.calculateAge();
}

@XmlElement(name = "birthday", required = true)
public Date getBirthday() {
return this.getPet().getDateOfBirth();
}
</pre>

@XmlType.propOrder specifies the order inwhich content is marshalled and unmarshalled. This order wouldotherwise be unspecified due to the nature of Java reflection.Alternatively, you can impose an order with the @XmlAccessorOrder annotation. As mentioned in the Java Tutorial, imposing an order improves application portability across JAXB implementations.

Although the field dateOfBirth is actually on the Petclass, it makes more sense in terms of our XML representation if thisdata is organized as an element under the birthday element. We canachieve this without a dateOfBirth field on the Birthday class by annotating the getBirthday() method, which delegates to Pet.

Ona related note, WePrintStuff doesn't need to do any additionalprocessing with the dates NiceVet sends them; they just need to locatethe correct notification template and print the information out. As wesaw earlier with the events list on the PrintOrder class,we can supply a custom adapter to get the desired XML output. Thistime, however, we'll specify the adapter through a package-levelannotation. The adapter will take in all java.util.Date instances and output nicely formatted Strings. The package-level annotation goes in the package-info.java file (located in the vet package):

<pre> // every java.util.Date class in the vet package should be
// processed by DateAdapter
@XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
package vet;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.Date;
</pre>

AlthoughJAXB can handle marshalling java.util.Date to XML in a default manneron its own, we're telling JAXB to use an alternative, customizedmapping which better suits our purposes. We implement the adapter inmuch the same way as above:

<pre> import java.util.Date;
import java.text.SimpleDateFormat;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<String, Date> {

// the desired format
private String pattern = "MM/dd/yyyy";

public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}

public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}

</pre>

Theannotations on the other classes in the object model are similar towhat we've seen so far; take a peek at the sample code included withthis tip to see exactly what's going on in the other classes.

We've finished annotating NiceVet's object model and we're ready togenerate some XML. If we run the sample application, we should see anXML instance document something like the following listing. Thedocument, niceVet.xml, is located in the root directory of thejava-to-schema project.

<pre> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<printOrder id="1218226109781">
<notifications>
<appointments>
<appointment>
<apptType>Yearly Checkup</apptType>
<apptDate>09/15/2008</apptDate>
<owner>
<firstName>Joe</firstName>
<lastName>Outdoors</lastName>
<address>
<addressLine1>123 Whitewater Street</addressLine1>
<city>OurTown</city>
<state>CA</state>
<zip>90347</zip>
<zipExt>1234</zipExt>
</address>
</owner>
<pet>
<name>Honcho</name>
<species>Dog</species>
</pet>
</appointment>
<appointment>
<apptType>Well Mom Exam</apptType>
<apptDate>09/12/2008</apptDate>
<owner>
<firstName>Missy</firstName>
<lastName>Fairchild</lastName>
<address>
<addressLine1>456 Scenic Drive</addressLine1>
<city>West OurTown</city>
<state>CA</state>
<zip>90349</zip>
<zipExt>6789</zipExt>
</address>
</owner>
<pet>
<name>Miss Kitty</name>
<species>Cat</species>
</pet>
</appointment>
</appointments>
<birthdays>
<birthday>
<age>7</age>
<birthday>09/07/2000</birthday>
<owner>
<firstName>Violet</firstName>
<lastName>Flowers</lastName>
<address>
<addressLine1>22375 Willow Court</addressLine1>
<city>West OurTown</city>
<state>CA</state>
<zip>90349</zip>
<zipExt>6789</zipExt>
</address>
</owner>
<pet>
<name>Tom</name>
<species>Cat</species>
</pet>
</birthday>
</birthdays>
</notifications>
</printOrder>

</pre>

The generated schema for the XML instance document above is named schema1.xsd, and it lives in /generated/schema under the root directory of the java-to-schema project. It looks like this:

<pre> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="printOrder" type="PrintOrderType"/>

<xs:complexType name="PrintOrderType">
<xs:sequence>
<xs:element name="notifications" type="notificationsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="id" type="xs:long" use="required"/>
</xs:complexType>

<xs:complexType name="notificationsType">
<xs:sequence>
<xs:element name="appointments" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="appointment" type="AppointmentType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="birthdays" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="birthday" type="BirthdayType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="AppointmentType">
<xs:sequence>
<xs:element name="apptType" type="xs:string"/>
<xs:element name="apptDate" type="xs:string"/>
<xs:element name="owner" type="OwnerType"/>
<xs:element name="pet" type="PetType"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="OwnerType">
<xs:sequence>
<xs:element name="firstName" type="xs:string"/>
<xs:element name="lastName" type="xs:string"/>
<xs:element name="address" type="AddressType"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="addressLine1" type="xs:string"/>
<xs:element name="addressLine2" type="xs:string" minOccurs="0"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="state" type="xs:string"/>
<xs:element name="zip" type="xs:string"/>
<xs:element name="zipExt" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="PetType">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="species" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="BirthdayType">
<xs:sequence>
<xs:element name="age" type="xs:int"/>
<xs:element name="birthday" type="xs:string"/>
<xs:element name="owner" type="OwnerType"/>
<xs:element name="pet" type="PetType"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

</pre>

If we take a quick peek at the main() method, we can see that we are generating the schema as follows:

<pre> // specify where the generated XML schema will be created
final File dir = new File("generated" + File.separator + "schema");
// specify a name for the generated XML instance document
OutputStream os = new FileOutputStream("niceVet.xml");

// create a JAXBContext for the PrintOrder class
JAXBContext ctx = JAXBContext.newInstance(PrintOrder.class);
// generate an XML schema from the annotated object model; create it
// in the dir specified earlier under the default name, schema1.xsd
ctx.generateSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(String namespaceUri, String schemaName) throws IOException {
return new StreamResult(new File(dir, schemaName));
}
});
</pre>

To get the XML instance document, we first create some data, then we obtain a Marshaller from the JAXBContext and marshal the Java content tree to XML:

<pre> // create some data
PrintOrder order = new PrintOrder(EventUtil.getEvents());

...

// create a marshaller
Marshaller marshaller = ctx.createMarshaller();
// the property JAXB_FORMATTED_OUTPUT specifies whether or not the
// marshalled XML data is formatted with linefeeds and indentation
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// marshal the data in the Java content tree
// to the XML instance document niceVet.xml
marshaller.marshal(order, os);
</pre>

Running the Sample Application


To run the sample application, downloadthe sample code and unzip it. Launch NetBeans and select File ->Open Project. In the Open Project dialog box, navigate to the directorywhere you unzipped the sample code and select the folderjava-to-schema. Select the Open as Main Project check box. Click OpenProject Folder. Right-click the java-to-schema project and select RunProject.

Conclusion

In this tip, you've seen howJAXB makes it easy to generate XML data to exchange with a businesspartner. The XML data your partner receives, however, may need a littletweaking in order to mesh with their system. In a future tip, you'lllearn how to apply customizations to a supplied schema to get the Javaobject model - and content tree - you want.

References and Resources

  • Sample code for this tip
  • The Java Tutorial.
  • Kohsuke Kawaguchi's Blog. Kohsuke Kawaguchi is a staff engineer at Sun Microsystems, where among other projects he works on JAXB.
  • Jennie Hall is a lead developer working in the financial sector.


    반응형

    'Computer > XML' 카테고리의 다른 글

    [JAXB] 실제 활용하기.  (1) 2009.12.17
    [JAXB] XML Binding을 위한 JAXB 참고 URL  (0) 2009.11.11