update - see edit at the bottom
IDRefs/keyrefs seem to be possible in JAXB annotations, but the ref ends up being element text.
I would like the ref to be an attribute of an element.
For example, given this object model:
@XmlType
public class Employee {
@XmlID
@XmlAttribute
String name;
@XmlAttribute
int years;
@XmlAttribute
String foo;
}
@XmlType
public class Office {
@XmlAttribute
String name;
@XmlElementWrapper
@XmlElement(name = "employee")
List<Employee> employees;
}
@XmlRootElement
public class Company {
@XmlElementWrapper
@XmlElement(name = "office")
List<Office> offices;
@XmlElementWrapper
@XmlElement(name = "employee")
List<Employee> employees;
}
I would like the externalized xml format to end up looking like this:
<company>
<offices>
<office name="nyc">
<employees>
<!--*** id ref to employee name ***-->
<employee ref="alice"/>
<employee ref="bob"/>
</employees>
</office>
<office name="sf">
<employees>
<employee ref="connie"/>
<employee ref="daphne"/>
</employees>
</office>
</offices>
<employees>
<!-- *** name is the id *** -->
<employee name="alice" years="3" foo="bar"/>
<employee name="bob" years="3" foo="bar"/>
<employee name="connie" years="3" foo="bar"/>
<employee name="daphne" years="3" foo="bar"/>
</employees>
</company>
Instead, the best I can do is this (with the annotations listed above in the java code):
<company>
<offices>
<office name="nyc">
<employees>
<employee>alice</employee>
<employee>bob</employee>
</employees>
</office>
<office name="sf">
<employees>
<employee>connie</employee>
<employee>daphne</employee>
</employees>
</office>
</offices>
<employees>
<employee name="alice" years="3" foo="bar"/>
<employee name="bob" years="3" foo="bar"/>
<employee name="connie" years="3" foo="bar"/>
<employee name="daphne" years="3" foo="bar"/>
</employees>
</company>
Is there a way I can force the idref value to be an attribute of employee, rather than element body text? I know I can do this with an XML Schema, but I'd like to stick to annotations if at all possible.
Thank you.
Edit The solution by Torious below almost works, but it doesn't quite work under some circumstances.
unmarshalling fails if the "offices" elements come before (in the xml file) the "employee" elements that the office references. The employee references are not found, and the EmployeeRef wrapper has a null employee object. If the "employee" are first, it works.
This wouldn't be so much of a problem, but the marshal method will put "offices" first, so that trying to unmarshal what has just been marshalled fails.
Edit 2 comment in Torious' answer solves the ordering problem.
The solution is to use an XmlAdapter
which wraps an Employee
in an instance of a new type, EmployeeRef
, which specifies how to map the XML id ref:
@XmlType
public class Office {
@XmlAttribute
String name;
@XmlElementWrapper
@XmlElement(name="employee")
@XmlJavaTypeAdapter(EmployeeAdapter.class) // (un)wraps Employee
List<Employee> employees;
}
@XmlType
public class EmployeeRef {
@XmlIDREF
@XmlAttribute(name="ref")
Employee employee;
public EmployeeRef() {
}
public EmployeeRef(Employee employee) {
this.employee = employee;
}
}
public class EmployeeAdapter extends XmlAdapter<EmployeeRef, Employee> {
@Override
public EmployeeRef marshal(Employee employee) throws Exception {
return new EmployeeRef(employee);
}
@Override
public Employee unmarshal(EmployeeRef ref) throws Exception {
return ref.employee;
}
}
Good luck.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is an example of how this can be done with MOXy by leveraging the @XmlPath
annotation. Due to a bug, you will need to use an EclipseLink 2.4.0 nightly label starting with May 17, 2012. This fix has also be added to the EclipseLink 2.3.3 (starting with May 18, 2012) stream. You can download a nightly label from the following location:
Office
On the employees
property you can use the @XmlIDREF
annotation in combination with the @XmlPath
annotation to get the desired mapping. @XmlIDREF
tells the JAXB implementation to write out a foreign key instead of the object.
package forum10150263;
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;
@XmlType
public class Office {
@XmlAttribute
String name;
@XmlPath("employees/employee/@ref")
@XmlIDREF
List<Employee> employees;
}
Employee
The counterpart to @XmlIDREF
is @XmlID
. @XmlID
is used to specify the primary key for an object.
package forum10150263;
import javax.xml.bind.annotation.*;
@XmlType
public class Employee {
@XmlID
@XmlAttribute
String name;
@XmlAttribute
int years;
@XmlAttribute
String foo;
}
Company
Each object referenced via the @XmlIDREF
mechanism also needs to be referenced by a containment relationship. In this example this is accomplished by the employees
property.
package forum10150263;
import java.util.List;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class Company {
@XmlElementWrapper
@XmlElement(name = "office")
List<Office> offices;
@XmlElementWrapper
@XmlElement(name = "employee")
List<Employee> employees;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to add a file named jaxb.properties
in the same package as your domain model with the following entry (see Specifying EclipseLink MOXy as Your JAXB Provider)
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
The standard JAXB APIs are used for unmarshalling/marshalling the XML.
package forum10150263;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Company.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum10150263/input.xml");
Company company = (Company) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(company, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<company>
<offices>
<office name="nyc">
<employees>
<employee ref="alice"/>
<employee ref="bob"/>
</employees>
</office>
<office name="sf">
<employees>
<employee ref="connie"/>
<employee ref="daphne"/>
</employees>
</office>
</offices>
<employees>
<employee name="alice" years="3" foo="bar"/>
<employee name="bob" years="3" foo="bar"/>
<employee name="connie" years="3" foo="bar"/>
<employee name="daphne" years="3" foo="bar"/>
</employees>
</company>
For More Information
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With