Jaxb Inheritance using Substitution but not to root Element - java

I was going through the Blaise's Blog http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html for Jaxb Inheritance using Substitution.
I want to implement the same but not to the root element. I am looking this type of XML as a output.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
<customer>
<address>
<street>1 A Street</street>
</address>
<address>
<street>2 B Street</street>
</address>
<phoneNumber>
<mobileNo>xxx-xxx-xxxx</mobileNo>
</phoneNumber>
</customer>
</configuration>
Following is the Configuration.java
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Configuration {
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
#Override
public String toString() {
return "\n Customer[ customer="+customer+"]";
}
}
Customer.java
public class Customer {
private List<ContactInfo> contactInfo;
#XmlElementRef
public List<ContactInfo> getContactInfo() {
return contactInfo;
}
public void setContactInfo(List<ContactInfo> contactInfo) {
this.contactInfo = contactInfo;
}
}
Address.java
public class Address extends ContactInfo {
private String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
PhoneNumber.java
public class PhoneNumber extends ContactInfo{
private String mobileNo;
public String getMobileNo() {
return mobileNo;
}
public void setMobileNo(String mobileNo) {
this.mobileNo = mobileNo;
}
}
Demo.java
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
Customer customer = new Customer();
List<ContactInfo> contacts = new ArrayList<ContactInfo>();
Address address = new Address();
address.setStreet("1 A Street");
contacts.add(address);
Address address1 = new Address();
address1.setStreet("2 B Street");
contacts.add(address1);
PhoneNumber phone = new PhoneNumber();
phone.setMobileNo("408 431 8829");
contacts.add(phone);
customer.setContactInfo(contacts);
configuration.setCustomer(customer);
JAXBContext jc = JAXBContext.newInstance(Configuration.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(configuration, System.out);
}
}
Presently I am getting following Exception
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Invalid #XmlElementRef : Type "class Address" or any of its subclasses are not known to this context.
Could anybody help me out on this?
Thanks,
Kwatra

Issue #1 - The Subclasses
A JAXB (JSR-222) implementation can not auto discover subclasses. You can solve the first exception by using an #XmlSeeAlso annotation on the ContactInfo class to reference the subclasses:
#XmlSeeAlso({Address.class, PhoneNumber.class})
public class ContactInfo {
}
Or you can reference them when you create the JAXBContext.
JAXBContext jc = JAXBContext.newInstance(Configuration.class, Address.class, PhoneNumber.class);
Issue #2 - The Mapping
When using #XmlElementRef you need to pair it with #XmlRootElement. If you don't want to go this route you could use #XmlElements instead.
#XmlElements({
#XmlElement(name="address", type=Address.class),
#XmlElement(name="phoneNumber", type=PhoneNumber.class)
})
public List<ContactInfo> getContactInfo() {
return contactInfo;
}
http://blog.bdoughan.com/2010/10/jaxb-and-xsd-choice-xmlelements.html

Related

How to use setters in JAXB collections unmarshalling

I wan't to deserialize XML to my POJO but something doing wrong...
My POJO class:
#Builder
#ToString
#AllArgsConstructor
#NoArgsConstructor
#XmlRootElement(name="taxi")
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(propOrder = {"id", "name", "phone", "citiesId"})
public class TaxiEntity {
#Getter #Setter
private Integer id;
#Getter #Setter
private String name;
#Getter #Setter
private String phone;
#Singular("city")
private Set<Integer> citiesId = new HashSet<>();
#XmlElementWrapper(name="cities_id")
#XmlElement(name="city_id")
public void setCitiesId(Set<Integer> citiesId) {
System.out.println("setCitiesId()");
this.citiesId = citiesId;
}
public Set<Integer> getCitiesId() {
System.out.println("getCitiesId()");
return new HashSet<>(citiesId);
}
}
Marshalling example:
JAXBContext context = JAXBContext.newInstance(TaxiEntity.class);
TaxiEntity entity = TaxiEntity.builder().
id(5).
name("my city").
phone("12345678").
city(1).
city(5).
build();
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(entity, new File("entity.xml"));
XML output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<taxi>
<id>5</id>
<name>my city</name>
<phone>12345678</phone>
<cities_id>
<city_id>1</city_id>
<city_id>5</city_id>
</cities_id>
</taxi>
Unmarshalling example:
JAXBContext context = JAXBContext.newInstance(TaxiEntity.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
TaxiEntity entity = (TaxiEntity) unmarshaller.unmarshal(new File("entity.xml"));
System.out.println(entity);
Console output:
getCitiesId()
getCitiesId()
TaxiEntity(id=5, name=my city, phone=12345678, citiesId=[])
Process finished with exit code 0
As you can see, citiesId is empty.
It happens because JAXB unmarshalling calling the getter (copy of field in my case)
and trying to set values into a copy of collection.
How to make it create a collection and set it via setter?
P.S. In my real bussiness object, i have collect IDs in getter from DB entities, and cannot return collection in getter.
Thanks!
---- Edited last time -----
import java.io.File;
import java.util.Set;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import lombok.Builder;
import lombok.Singular;
import lombok.ToString;
#Builder
#ToString
#XmlRootElement(name = "taxi")
#XmlType(name="taxi", propOrder = { "id", "name", "phone", "citiesId" })
public class TaxiEntity {
private Integer id;
private String name;
private String phone;
#Singular("city")
private Set<Integer> citiesId;
public TaxiEntity() {
}
public TaxiEntity(Integer id, String name, String phone, Set<Integer> citiesId) {
System.out.println("Hello");
this.id = id;
this.name = name;
this.phone = phone;
this.citiesId = citiesId;
}
#XmlElementWrapper(name = "cities_id")
#XmlElement(name = "city_id")
public void setCitiesId(Set<Integer> citiesId) {
System.out.println("I should be calling during deserialization" + citiesId);
this.citiesId = citiesId;
}
#XmlElement
public void setId(Integer id) {
this.id = id;
}
#XmlElement
public void setName(String name) {
this.name = name;
}
#XmlElement
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public Set<Integer> getCitiesId() {
System.out.println("Calling getter " + this.citiesId);
return citiesId;
}
public static void main(String[] args) {
try {
JAXBContext context = JAXBContext.newInstance(TaxiEntity.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
Unmarshaller unmarshaller = context.createUnmarshaller();
TaxiEntity entity = TaxiEntity.builder().id(5).name("my city").phone("12345678").city(1).city(5).build();
marshaller.marshal(entity, new File("C:/whee/entity.xml"));
System.out.println("Unmarshalling now ------");
TaxiEntity taxEntityWithSettersGetters = (TaxiEntity) unmarshaller.unmarshal(new File("C:/whee/entity.xml"));
System.out.println(taxEntityWithSettersGetters);
} catch (Exception e) {
e.printStackTrace();
}
}
Printout:
Hello
Calling getter [1, 5]
Unmarshalling now ------
Calling getter null
I should be calling during deserialization[]
Calling getter [1, 5]
TaxiEntity(id=5, name=my city, phone=12345678, citiesId=[1, 5])
During unmarshalling JAXB checks if your collection is null, if it is (It will call the setter for the first time to initialize it to empty), and you can see that in the log.
However, afterwards, it will use its internal logic to populate the collection (SET), initialize its type (New Set)*by using the Setter you have, and use the Set.add(xyz); to add (1), then (5).
The JAXB Logic invoked is found in class:
public abstract class Lister<BeanT,PropT,ItemT,PackT> {
//startPacking is calling to initialize the collection Set so it is empty
public T startPacking(BeanT bean, Accessor<BeanT, T> acc) throws AccessorException {
T collection = acc.get(bean);
if(collection==null) {
collection = ClassFactory.create(implClass);
if(!acc.isAdapted())
acc.set(bean,collection);
}
collection.clear();
return collection;
}
//Right way, this gets called afterwards (Before any of your TaxiEntity logic), to do addToPack(1), addToPack(5), <--- Now your Set has [1,5]
public void addToPack(T collection, Object o) {
collection.add(o);
}
Then, you see in the log, it calls getCitiesIds(), and you will see magically it has [1,5]
Its the way JAXB works with Collections. All other elements, their proper Setters are called.
See, JAXB does not call Setter method
You need to think of a different way of doing it, rather than dependending on the getter/setter. It did its job of unmarshalling the object from the XML file, the rest of the logic could be written in an external method.

JAXB unmarshal mystery XML

I am using JAXB to unmarshal an XML file.
All I know about the XML file is that it is valid XML.
How then am I supposed to specify a class and/or package to newInstance?
JAXBContext jaxbContext = JAXBContext.newInstance(??????);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Object o = (Object) unmarshaller.unmarshal(myFile);
I did not see anything in the docs that address this issue.
You need to tell JaxB what class to unmarshall to so that it can use the annotations in the class to resolve the hierarchy of the xml. You will need to have a class that is also annotated with something like #XmlRootElement. If you want to parse arbitrary xml you will probably need to do something with a DocumentBuilder or xpath.
See this artical for more info.
http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html
I have used something like this to convert arbitrary xml to a class. The any field will actually be a list of org.w3c.dom.Element in which you can get information from.
http://docs.oracle.com/javase/7/docs/api/org/w3c/dom/Element.html
#XmlRootElement
class Wrapper {
/**
* Everything else
*/
#Transient
#XmlAnyElement(lax = true)
private List<Element> any;
public List<Element> getAny() {
return any;
}
}
In newInstance you must add the class root element that map your xml... below an example
Here an example ..
public static void main(String[] args) throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(Vehicals.class);
final Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
final Vehicals vehicals = new Vehicals();
List<Car> cars = new ArrayList<Car>();
Car c = new Car();
c.setName("Mercedes");
cars.add(c);
c = new Car();
c.setName("BMW");
cars.add(c);
vehicals.setCar(cars);
m.marshal(vehicals, System.out);
}
Vehicals.java
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Vehicals {
private List<Car> Car;
public List<Car> getCar() {
return Car;
}
public void setCar(List<Car> cars) {
this.Car = cars;
}
}
Car.java
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
#XmlRootElement
public class Car {
#XmlTransient
private Long id;
private String name;
#XmlTransient
private String code;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
output.xml
<Vehicle>
<Car>
<name>Mercedes</name>
</Car>
<Car>
<name>BMW</name>
</Car>
</Vehicle>
For the Unmarshal is the same thing. In my case i added Vehicals as parameter in newInstance method.

JAXB/MOXy use same Entity-type for several properties

I have a rather large object tree which I want to export to XML. An object named Person is used at several places (as userCreated, userModified of many child entities, as client, etc)
Depending of the use of the Person-object I need to have different elements in the xml. Example:
<policy>
<userCreated>
<firstName>John</firstName>
<lastName>Doe</lastName>
</userCreated>
<client>
<clientId>1234</clientId>
<email>jdoe#example.com</email>
<firstName>John</firstName>
<lastName>Doe</lastName>
</client>
</policy>
userCreated and client are instances of the same object (named Person)
How can this be set up in bindings.xml ?
You can use EclipseLink JAXB (MOXy)'s #XmlNamedObjectGraph extension to support this use case. What #XmlNamedObjectGraph allows you to do is create multiple views on your data.
Person
Below we will use #XmlNamedObjectGraph to create a view on the Person class that only exposes 2 fields (firstName and lastName).
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#XmlNamedObjectGraph(
name = "simple",
attributeNodes = {
#XmlNamedAttributeNode("firstName"),
#XmlNamedAttributeNode("lastName")
}
)
#XmlAccessorType(XmlAccessType.FIELD)
public class Person {
private int clientId;
private String firstName;
private String lastName;
private String email;
public void setClientId(int clientId) {
this.clientId = clientId;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setEmail(String email) {
this.email = email;
}
}
Policy
We will also use #XmlNamedObjectGraph on the Policy class. It says that for the userCreated field apply the named object graph called simple that we defined on the Person class.
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlNamedObjectGraph(
name = "policy",
attributeNodes = {
#XmlNamedAttributeNode(value = "userCreated", subgraph = "simple"),
#XmlNamedAttributeNode("client")
}
)
public class Policy {
private Person userCreated;
private Person client;
public void setUserCreated(Person userCreated) {
this.userCreated = userCreated;
}
public void setClient(Person client) {
this.client = client;
}
}
Demo
In the demo code below we will specify the named object graph that we want applied on the Marshaller using the MarshallerProperties.OBJECT_GRAPH property.
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Policy.class);
Person person = new Person();
person.setClientId(1234);
person.setFirstName("John");
person.setLastName("Doe");
person.setEmail("jdoe#example.com");
Policy policy = new Policy();
policy.setClient(person);
policy.setUserCreated(person);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "policy");
marshaller.marshal(policy, System.out);
}
}
Output
Below is the output from running the demo code:
<?xml version="1.0" encoding="UTF-8"?>
<policy>
<userCreated>
<firstName>John</firstName>
<lastName>Doe</lastName>
</userCreated>
<client>
<clientId>1234</clientId>
<firstName>John</firstName>
<lastName>Doe</lastName>
<email>jdoe#example.com</email>
</client>
</policy>
For More Information
http://blog.bdoughan.com/2013/03/moxys-object-graphs-inputoutput-partial.html
http://blog.bdoughan.com/2013/03/moxys-object-graphs-partial-models-on.html

JAXB - Unexpected elements in XML output

I have a class called Building.
It has a list of BuildingBenchAssociation records (List<BuildingBenchAssociation> benches)
BuildingBenchAssociation has a composite id made up of buildingId and benchId
The ID is represented by separate class called BuildingBenchAssociationPKwhich has only two properties - buildingId and benchId
This is the output I get when I marshal a Building instance
<building buildingId="9">
<benches>
DOMRecord(<?xml version="1.0" encoding="UTF-8"?><buildingBenchAssociation><benchId>245865</benchId><buildingId>9</buildingId></buildingBenchAssociation>)
</benches>
<benches>
DOMRecord(<?xml version="1.0" encoding="UTF-8"?><buildingBenchAssociation><benchId>245866</benchId><buildingId>9</buildingId></buildingBenchAssociation>)
</benches>
<benches>
But I don't want DOMRecord(<?xml version="1.0" encoding="UTF-8"?> to appear in the output. Required output is something like this:
<building buildingId="9">
<benches>
<buildingBenchAssociation><benchId>245865</benchId><buildingId>9</buildingId></buildingBenchAssociation>
</benches>
<benches>
<buildingBenchAssociation><benchId>245866</benchId><buildingId>9</buildingId></buildingBenchAssociation>
</benches>
<benches>
What's wrong and how do I correct it? I am using the Eclipselink MOXy library.
Classes for reference:
Class 1
#Entity
#Table(name="building")
#XmlRootElement
public class Building implements Serializable {
....
private List<BuildingBenchAssociation> benchs = new ArrayList<BuildingBenchAssociation>();
#XmlIDREF
#OneToMany(mappedBy="building")
public List<BuildingBenchAssociation> getBenchs() {
return benchs;
}
public void setBenchs(List<BuildingBenchAssociation> benchs) {
this.benchs = benchs;
}
}
Class 2
#Entity
#Table(name="building_bench_rel")
#XmlRootElement
public class BuildingBenchAssociation implements Serializable {
private static final long serialVersionUID = 1L;
private BuildingBenchAssociationPK idx;
private Bench bench;
private Building building;
private byte alertFlags;
private byte status;
public BuildingBenchAssociation() {
idx=new BuildingBenchAssociationPK();
}
#XmlID
#XmlPath(".")
#Id
public BuildingBenchAssociationPK getIdx() {
return this.idx;
}
public void setIdx(BuildingBenchAssociationPK id) {
this.idx = id;
}
#Column(name="ALERT_FLAGS")
public byte getAlertFlags() {
return this.alertFlags;
}
public void setAlertFlags(byte alertFlags) {
this.alertFlags = alertFlags;
}
#Column(name="STATUS", insertable=false, updatable=false)
public byte getStatus() {
return this.status;
}
public void setStatus(byte status) {
this.status = status;
}
#XmlIDREF
#ManyToOne
#JoinColumn(name="BENCH_ID",insertable=false,updatable=false)
public Bench getBench() {
return bench;
}
public void setBench(Bench bench) {
this.bench = bench;
this.idx.setBenchId(bench==null?null:bench.getBenchId());
}
#XmlIDREF
#ManyToOne
#JoinColumn(name="BUILDING_ID",insertable=false,updatable=false)
public Building getBuilding() {
return building;
}
public void setBuilding(Building building) {
this.building = building;
this.idx.setBuildingId(building==null?null:building.getBuildingId());
}
}
Class 3
#Embeddable
#XmlRootElement
public class BuildingBenchAssociationPK implements Serializable {
...
private Integer buildingId;
private Integer benchId;
public BuildingBenchAssociationPK() {
}
#XmlKey
#Column(name="BUILDING_ID")
public Integer getBuildingId() {
return this.buildingId;
}
public void setBuildingId(Integer buildingId) {
this.buildingId = buildingId;
}
#XmlKey
#Column(name="BENCH_ID")
public Integer getBenchId() {
return this.benchId;
}
public void setBenchId(Integer benchId) {
this.benchId = benchId;
}
}
Below is how you can currently map this use case using MOXy. I have opened the following enhancement request to make this use case easier to map:
http://bugs.eclipse.org/407460
REFERENCED OBJECT
Embedded ID (EmployeeId)
Below is an example of an embedded ID class:
import java.math.BigDecimal;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
#Embeddable
#XmlAccessorType(XmlAccessType.FIELD)
public class EmployeeId {
#Column(name="E_ID")
BigDecimal eId;
String country;
}
Class with Embedded ID (Employee)
We want to use the embedded ID class for as the key in an XML relationship. Currently MOXy does not allow this to be done via annotations, so we will leverage the #XmlCustomizer annotation to programmatically modify the metadata.
import java.util.List;
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#Entity
#IdClass(EmployeeId.class)
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(EmployeeCustomizer.class)
public class Employee {
#EmbeddedId
#XmlPath(".")
EmployeeId id;
#OneToMany(mappedBy="contact")
List<PhoneNumber> contactNumber;
}
Customize Mapping Metadata for Employee (EmployeeCustomizer)
In the customizer class we will specify the XPaths for the mappings that compose the key on the embedded class.
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
public class EmployeeCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.addPrimaryKeyFieldName("eId/text()");
descriptor.addPrimaryKeyFieldName("country/text()");
}
}
REFERRING OBJECT
PhoneNumber
We are also going to need to programatically add the mapping based on the composite key, so once again we will use the #XmlCustomizer annotation.
import javax.persistence.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
#Entity
#XmlAccessorType(XmlAccessType.FIELD)
#XmlCustomizer(PhoneNumberCustomizer.class)
public class PhoneNumber {
#ManyToOne
#JoinColumns({
#JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
#JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
})
Employee contact;
}
Customize Mapping Metadata for PhoneNumber (PhoneNumberCustomizer)
In this customizer we will remove the default mapping, and programatically create the new one based on the composite keys.
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
public class PhoneNumberCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.removeMappingForAttributeName("contact");
XMLObjectReferenceMapping contactMapping = new XMLObjectReferenceMapping();
contactMapping.setAttributeName("contact");
contactMapping.setReferenceClass(Employee.class);
contactMapping.addSourceToTargetKeyFieldAssociation("contact/#eID", "eId/text()");
contactMapping.addSourceToTargetKeyFieldAssociation("contact/#country", "country/text()");
descriptor.addMapping(contactMapping);
}
}
DEMO CODE
The following demo code can be used to demonstrate that everything works:
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Employee.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
Employee employee = (Employee) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(employee, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<employee>
<eId>10</eId>
<country>Canada</country>
<contactNumber>
<contact eID="10" country="Canada"/>
</contactNumber>
<contactNumber>
<contact eID="10" country="Canada"/>
</contactNumber>
</employee>
FOR MORE INFORMATION
http://wiki.eclipse.org/EclipseLink/Examples/MOXy/JPA/EmbeddedIdClass

Default namespace & complex package/data structure

I need create/read xml file using default namespace:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xmlBoo xmlns="http://www.example2.org/boo">
<customer>
<address>
<street>Wall Street</street>
</address>
<id>1</id>
<name>John</name>
</customer>
<someSpecificField>Specific data in Boo</ns2:someSpecificField>
</xmlBoo>
but I'm getting:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:xmlBoo xmlns:ns2="http://www.example2.org/boo">
<ns2:customer>
<address>
<street>Wall Street</street>
</address>
<id>1</id>
<name>John</name>
</ns2:customer>
<ns2:someSpecificField>Specific data in Boo</ns2:someSpecificField>
</ns2:xmlBoo>
I know about package level metadata, but this is not working in complex package structure:
I have defined model classes like Address:
package example.model;
public class Address {
private String street;
Customer:
package example.model;
public class Customer {
private long id;
private String name;
private Address address;
The parent class for common fields:
package example.xml;
#XmlTransient
public class Xml {
private Customer customer;
Then specific classes which holds data/structure of concrete xml XmlBoo:
package example.xml.boo;
#XmlRootElement
public class XmlBoo extends Xml {
private String someSpecificField;
XmlFoo:
package example.xml.foo;
#XmlRootElement
public class XmlFoo extends Xml {}
package-info.java is included in two mentioned packages example.xml.boo:
#XmlSchema(
namespace = "http://www.example2.org/boo",
elementFormDefault = XmlNsForm.QUALIFIED)
package example.xml.boo;
and example.xml.foo:
#XmlSchema(
namespace = "http://www.example2.org/foo",
elementFormDefault = XmlNsForm.QUALIFIED)
package example.xml.foo;
And finally main method:
package example;
public class Demo {
public static void main(String... args) {
generateBoo();
generateFoo();
}
public static void generateBoo() {
try {
JAXBContext jc = JAXBContext.newInstance(XmlBoo.class);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
XmlBoo xmlBoo = new XmlBoo();
Customer customer = new Customer();
customer.setId(1);
customer.setName("John");
Address address = new Address();
address.setStreet("Wall Street");
customer.setAddress(address);
xmlBoo.setCustomer(customer);
xmlBoo.setSomeSpecificField("Specific data in Boo");
m.marshal(xmlBoo, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
public static void generateFoo() {
try {
JAXBContext jc = JAXBContext.newInstance(XmlFoo.class);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
XmlFoo xmlFoo = new XmlFoo();
Customer customer = new Customer();
customer.setId(1);
customer.setName("John");
Address address = new Address();
address.setStreet("Wall Street");
customer.setAddress(address);
xmlFoo.setCustomer(customer);
m.marshal(xmlFoo, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
I've tried both solutions like here and also without success.
It is possible remove & rename prefix if I have all classes in one package (and one package-info file)
It is possible rename but NOT remove prefix if I have complex package structure
Is there solution how I can remove ns2 prefix?
I'm using JDK7.
Solution how get (write & read xml) the needed result:
<?xml version="1.0" encoding="UTF-8"?>
<xmlBoo xmlns="http://www.example.org/boo" xmlns:c="http://www.example.org/customer" xmlns:a="http://www.example.org/address" xmlns:h="http://www.example.org/header">
<h:header>
<h:id>101</h:id>
</h:header>
<c:customer>
<c:id>1</c:id>
<c:name>Yen</c:name>
<a:address>
<a:street>Long street</a:street>
</a:address>
</c:customer>
<someBooSpecificField>Specific data in Boo</someBooSpecificField>
</xmlBoo>
for root element and its "simple" children is used default namespace (without prefix)
for complex (objects in java) children are used different namespaces (mapped to different prefixes)
model classes are in different packages
So here is the solution:
Define MOXy implementation of JAXB, file: jaxb.properties
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Create abstract class for common fields, define namespace for object, file Xml.java
package example.xml;
#XmlTransient
public abstract class Xml {
private Header header;
private Customer customer;
#XmlElement(namespace="http://www.example.org/header")
public Header getHeader() {
return header;
}
public void setHeader(Header header) {
this.header = header;
}
#XmlElement(namespace="http://www.example.org/customer")
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
Create "root" class, XmlBoo.java
package example.xml.boo;
#XmlRootElement
#XmlType(propOrder = {"header", "customer", "someBooSpecificField"})
public class XmlBoo extends Xml {
private String someBooSpecificField;
// getter & setter
}
Set namespace and QUALIFIED for "root" class, file: example.xml.boo.package-info.java
#XmlSchema(
namespace = "http://www.example.org/boo",
elementFormDefault = XmlNsForm.QUALIFIED)
package example.xml.boo;
Set QUALIFIED to generate prefix for children (the namespace will be overridden by namespace defined on the class, but it must be defined), file: example.model.package-info.java
#XmlSchema(
namespace = "http://www.example.org",
elementFormDefault = XmlNsForm.QUALIFIED)
package example.model;
Create Header.java
package example.model;
#XmlType(namespace = "http://www.example.org/header")
public class Header {
private long id;
// getter & setter
}
Create Customer.java
package example.model;
#XmlType(namespace = "http://www.example.org/customer", propOrder = {"id", "name", "address"})
public class Customer {
private long id;
private String name;
private Address address;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlElement(namespace="http://www.example.org/address")
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Create Address.java
package example.model;
#XmlType(namespace = "http://www.example.org/address")
public class Address {
private String street;
// getter & setter
}
Create MyNamespacePrefixMapper.java by extending org.eclipse.persistence.oxm.NamespacePrefixMapper
package example;
import org.eclipse.persistence.oxm.NamespacePrefixMapper;
public class MyNamespacePrefixMapper extends NamespacePrefixMapper {
private static final String BOO_PREFIX = ""; // DEFAULT NAMESPACE
private static final String BOO_URI = "http://www.example.org/boo";
private static final String FOO_PREFIX = ""; // DEFAULT NAMESPACE
private static final String FOO_URI = "http://www.example.org/foo";
private static final String HEADER_PREFIX = "h";
private static final String HEADER_URI = "http://www.example.org/header";
private static final String CUSTOMER_PREFIX = "c";
private static final String CUSTOMER_URI = "http://www.example.org/customer";
private static final String ADDRESS_PREFIX = "a";
private static final String ADDRESS_URI = "http://www.example.org/address";
#Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
switch (namespaceUri) {
case BOO_URI:
return BOO_PREFIX;
case FOO_URI:
return FOO_PREFIX;
case HEADER_URI:
return HEADER_PREFIX;
case CUSTOMER_URI:
return CUSTOMER_PREFIX;
case ADDRESS_URI:
return ADDRESS_PREFIX;
default:
return null;
}
}
}
Create XML
public static void generateBoo() {
try {
JAXBContext jc = JAXBContext.newInstance(XmlBoo.class);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, new MyNamespacePrefixMapper());
XmlBoo xmlBoo = new XmlBoo();
Header header = new Header();
header.setId(101);
xmlBoo.setHeader(header);
Customer customer = new Customer();
customer.setId(1);
customer.setName("Yen");
Address address = new Address();
address.setStreet("Long street");
customer.setAddress(address);
xmlBoo.setCustomer(customer);
xmlBoo.setSomeBooSpecificField("Specific data in Boo");
m.marshal(xmlBoo, System.out);
m.marshal(xmlBoo, new File("xml_boo.xml"));
} catch (JAXBException e) {
e.printStackTrace();
}
}
Read XML
public static void readBoo() {
Object element = null;
try {
JAXBContext jc = JAXBContext.newInstance(XmlBoo.class);
Unmarshaller u = jc.createUnmarshaller();
element = u.unmarshal(new File("xml_boo.xml"));
} catch (JAXBException e) {
e.printStackTrace();
}
if (element instanceof XmlBoo) {
XmlBoo xmlBoo = (XmlBoo) element;
Customer customer = xmlBoo.getCustomer();
System.out.println("INFO | xmlBoo field: [" + xmlBoo.getSomeBooSpecificField() + "]");
System.out.println("INFO | customer name: [" + customer.getName() + "]");
System.out.println("INFO | address street: [" + customer.getAddress().getStreet() + "]");
}
}
I used EclipseLink MOXy JAXB implementation instead of RI Metro JAXB and now it works. So it looks that in Metro is bug.
Perfect tutorial by Blaise Doughan: JAXB & Namespace prefixes
You will need to have a package-info annotation with a #XmlSchema annotation for each package in your domain model each specifying the same namespace qualification to get the desired XML.

Categories

Resources