Reputation: 2813
I am trying to unmarshall a DICOM Sequence VR type which is stored in the database as an XML CLOB. As per the standard, the sequence can contain properties as well as sequences as children. An example for reference is the Content Sequence attribute.
As a result, I am using @XmlMixed
in my JAXB DTOs to handle the case where the child property may also be a sequence. However, on marshalling the unmarshalled XML, the resultant output is just the root element without the children.
Below is my input XML representing the aforementioned Content Sequence.
<Root>
<ps>
<p name="0040A010" vt="8">HAS CONCEPT MOD</p>
<p name="0040A040" vt="8">CODE</p>
<p name="0040A043" vt="8">
<ps>
<p name="00080100" vt="8">121049</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Language of Content Item and Descendants
</p>
</ps>
</p>
<p name="0040A168" vt="8">
<ps>
<p name="00080100" vt="8">eng</p>
<p name="00080102" vt="8">ISO639_2</p>
<p name="00080104" vt="8">English</p>
</ps>
</p>
</ps>
<ps>
<p name="0040A010" vt="8">HAS OBS CONTEXT</p>
<p name="0040A040" vt="8">CODE</p>
<p name="0040A043" vt="8">
<ps>
<p name="00080100" vt="8">121005</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Observer Type</p>
</ps>
</p>
<p name="0040A168" vt="8">
<ps>
<p name="00080100" vt="8">121006</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Person</p>
</ps>
</p>
</ps>
<ps>
<p name="0040A010" vt="8">HAS OBS CONTEXT</p>
<p name="0040A040" vt="8">PNAME</p>
<p name="0040A043" vt="8">
<ps>
<p name="00080100" vt="8">121008</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Person Observer Name</p>
</ps>
</p>
<p name="0040A123" vt="8">IMAGE</p>
</ps>
<ps>
<p name="00081199" vt="8">
<ps>
<p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.4</p>
<p name="00081155" vt="8">1.3.6.1.4.1.5962.99.1.3923360762.207819601.1541521685498.13.0
</p>
<p name="00081199" vt="8">
<ps>
<p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.11.1</p>
<p name="00081155" vt="8">1.2.840.114356.2019.12.115.113.18.116.1508.6
</p>
</ps>
</p>
<p name="00750010" vt="8">GEIIS_IW</p>
<p name="007510A1" vt="8">1</p>
</ps>
</p>
<p name="0040A010" vt="8">CONTAINS</p>
<p name="0040A040" vt="8">IMAGE</p>
</ps>
</Root>
And below are the classes used to map bind the above structure:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "p")
public class Property
{
@XmlElementRef(name = "ps", type = PropertySequence.class, required = false)
@XmlMixed
protected List<Object> content;
@XmlAttribute(name = "name", required = true)
protected String name;
public List<Object> getContent()
{
if (content == null)
{
content = new ArrayList<Object>();
}
return this.content;
}
public String getName()
{
return name;
}
public void setName(String value)
{
this.name = value;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "ps")
public class PropertySequence
{
protected List<Property> property;
public List<Property> getProperty()
{
if (property == null)
{
property = new ArrayList<Property>();
}
return this.property;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Root")
public class Root
{
@XmlElement(required = true)
protected List<PropertySequence> propertySequence;
public List<PropertySequence> getPropertySequence()
{
if (propertySequence == null)
{
propertySequence = new ArrayList<PropertySequence>();
}
return this.propertySequence;
}
}
On running the below test code, the output XML after unmarshalling and subsequent marshalling, the output is only the root tag.
try (InputStream xmlStream = Launcher.class.getResourceAsStream("/PropertySequence.xml"))
{
JAXBContext context = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(xmlStream);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(root, System.out);
}
catch (IOException | JAXBException e)
{
e.printStackTrace();
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root/>
Upvotes: 1
Views: 175
Reputation: 38665
Even with XmlElementRef
we are not able to deserialise value to PropertySequence
or String
directly. Anyway, much easier will be by using JAXBElement
. Let's see model:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Root", propOrder = {"ps"})
public class Root {
protected List<PropertySequence> ps;
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ps", propOrder = {"p"})
public class PropertySequence {
protected List<Property> p;
public List<Property> getP() {
if (p == null) {
p = new ArrayList<>();
}
return this.p;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Property property : p) {
builder.append(property).append(System.lineSeparator());
}
return builder.toString();
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "p", propOrder = {"content"})
public class Property {
@XmlMixed
@XmlElementRef(name = "ps", type = JAXBElement.class, required = false)
protected List<Serializable> content;
@XmlAttribute(name = "name")
protected String name;
@XmlAttribute(name = "vt")
protected String vt;
@XmlTransient
public String getStringValue() {
if (content != null && content.size() == 1) {
return content.get(0).toString();
}
return null;
}
@XmlTransient
public PropertySequence getPropertySequence() {
if (content != null && content.size() == 3) {
return ((JAXBElement<PropertySequence>) content.get(1)).getValue();
}
return null;
}
public List<Serializable> getContent() {
if (content == null) {
content = new ArrayList<>();
}
return this.content;
}
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getVt() {
return vt;
}
public void setVt(String value) {
this.vt = value;
}
@Override
public String toString() {
Object value = getStringValue();
if (value == null) {
value = getPropertySequence();
}
return "Property{" +
"content=" + value +
", name='" + name + '\'' +
", vt='" + vt + '\'' +
'}';
}
}
@XmlRegistry
public class ObjectFactory {
private final static QName ROOT_QNAME = new QName("", "Root");
private final static QName PropertySequence_QNAME = new QName("", "ps");
@XmlElementDecl(namespace = "", name = "Root")
public JAXBElement<Root> createRoot(Root value) {
return new JAXBElement<>(ROOT_QNAME, Root.class, null, value);
}
@XmlElementDecl(namespace = "", name = "ps", scope = Property.class)
public JAXBElement<PropertySequence> createPropertySequence(PropertySequence value) {
return new JAXBElement<>(PropertySequence_QNAME, PropertySequence.class, Property.class, value);
}
}
Simple example:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
public class JaxbApp {
public static void main(String[] args) throws Exception {
File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
JAXBContext context = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
JAXBElement<Root> root = (JAXBElement<Root>) unmarshaller.unmarshal(xmlFile);
System.out.println(root.getValue());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(root, System.out);
}
}
Prints model:
RootType{ps=[Property{content=HAS CONCEPT MOD, name='0040A010', vt='8'}
Property{content=CODE, name='0040A040', vt='8'}
Property{content=Property{content=121049, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Language of Content Item and Descendants, name='00080104', vt='8'}
, name='0040A043', vt='8'}
Property{content=Property{content=eng, name='00080100', vt='8'}
Property{content=ISO639_2, name='00080102', vt='8'}
Property{content=English, name='00080104', vt='8'}
, name='0040A168', vt='8'}
, Property{content=HAS OBS CONTEXT, name='0040A010', vt='8'}
Property{content=CODE, name='0040A040', vt='8'}
Property{content=Property{content=121005, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Observer Type, name='00080104', vt='8'}
, name='0040A043', vt='8'}
Property{content=Property{content=121006, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Person, name='00080104', vt='8'}
, name='0040A168', vt='8'}
, Property{content=HAS OBS CONTEXT, name='0040A010', vt='8'}
Property{content=PNAME, name='0040A040', vt='8'}
Property{content=Property{content=121008, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Person Observer Name, name='00080104', vt='8'}
, name='0040A043', vt='8'}
Property{content=IMAGE, name='0040A123', vt='8'}
, Property{content=Property{content=1.2.840.10008.5.1.4.1.1.4, name='00081150', vt='8'}
Property{content=1.3.6.1.4.1.5962.99.1.3923360762.207819601.1541521685498.13.0
, name='00081155', vt='8'}
Property{content=Property{content=1.2.840.10008.5.1.4.1.1.11.1, name='00081150', vt='8'}
Property{content=1.2.840.114356.2019.12.115.113.18.116.1508.6
, name='00081155', vt='8'}
, name='00081199', vt='8'}
Property{content=GEIIS_IW, name='00750010', vt='8'}
Property{content=1, name='007510A1', vt='8'}
, name='00081199', vt='8'}
Property{content=CONTAINS, name='0040A010', vt='8'}
Property{content=IMAGE, name='0040A040', vt='8'}
]}
It prints empty root because deserialising does not work properly for your mapping and root object is empty (propertySequence
is null
). You need to update POJO
mapping. Instead fo @XmlElementRef
annotation use @XmlAnyElement
. Model after few changes could look like below:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Root")
class Root {
@XmlElement(required = true, name = "ps")
protected List<PropertySequence> propertySequence;
// getters, setters, toString
}
@XmlAccessorType(XmlAccessType.FIELD)
class PropertySequence {
@XmlElement(name = "p")
protected List<Property> property;
// getters, setters, toString
}
@XmlAccessorType(XmlAccessType.FIELD)
class Property {
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "vt", required = true)
protected Integer vt;
@XmlMixed
@XmlAnyElement
protected List<Object> value;
// getters, setters, toString
}
And below code:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlMixed;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class JaxbApp {
public static void main(String[] args) throws Exception {
File xmlFile = new File("./resource/test.xml").getAbsoluteFile();
JAXBContext context = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Root root = (Root) unmarshaller.unmarshal(xmlFile);
System.out.println(root);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(root, System.out);
}
}
prints object:
Root{propertySequence=[PropertySequence{property=[Property{name='0040A010', vt=8, value=[HAS CONCEPT MOD]}, Property{name='0040A040', vt=8, value=[CODE]}, Property{name='0040A043', vt=8, value=[
, [ps: null],
]}, Property{name='0040A168', vt=8, value=[
, [ps: null],
]}]}, PropertySequence{property=[Property{name='0040A010', vt=8, value=[HAS OBS CONTEXT]}, Property{name='0040A040', vt=8, value=[CODE]}, Property{name='0040A043', vt=8, value=[
, [ps: null],
]}, Property{name='0040A168', vt=8, value=[
, [ps: null],
]}]}, PropertySequence{property=[Property{name='0040A010', vt=8, value=[HAS OBS CONTEXT]}, Property{name='0040A040', vt=8, value=[PNAME]}, Property{name='0040A043', vt=8, value=[
, [ps: null],
]}, Property{name='0040A123', vt=8, value=[IMAGE]}]}, PropertySequence{property=[Property{name='00081199', vt=8, value=[
, [ps: null],
]}, Property{name='0040A010', vt=8, value=[CONTAINS]}, Property{name='0040A040', vt=8, value=[IMAGE]}]}]}
and XML
:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
<ps>
<p name="0040A010" vt="8">HAS CONCEPT MOD</p>
<p name="0040A040" vt="8">CODE</p>
<p name="0040A043" vt="8">
<ps>
<p name="00080100" vt="8">121049</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Language of Content Item and Descendants</p>
</ps>
</p>
<p name="0040A168" vt="8">
<ps>
<p name="00080100" vt="8">eng</p>
<p name="00080102" vt="8">ISO639_2</p>
<p name="00080104" vt="8">English</p>
</ps>
</p>
</ps>
<ps>
<p name="0040A010" vt="8">HAS OBS CONTEXT</p>
<p name="0040A040" vt="8">CODE</p>
<p name="0040A043" vt="8">
<ps>
<p name="00080100" vt="8">121005</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Observer Type</p>
</ps>
</p>
<p name="0040A168" vt="8">
<ps>
<p name="00080100" vt="8">121006</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Person</p>
</ps>
</p>
</ps>
<ps>
<p name="0040A010" vt="8">HAS OBS CONTEXT</p>
<p name="0040A040" vt="8">PNAME</p>
<p name="0040A043" vt="8">
<ps>
<p name="00080100" vt="8">121008</p>
<p name="00080102" vt="8">DCM</p>
<p name="00080104" vt="8">Person Observer Name</p>
</ps>
</p>
<p name="0040A123" vt="8">IMAGE</p>
</ps>
<ps>
<p name="00081199" vt="8">
<ps>
<p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.4</p>
<p name="00081155" vt="8">1.3.6.1.4.1.5962.99.1.3923360762.207819601.1541521685498.13.0
</p>
<p name="00081199" vt="8">
<ps>
<p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.11.1</p>
<p name="00081155" vt="8">1.2.840.114356.2019.12.115.113.18.116.1508.6
</p>
</ps>
</p>
<p name="00750010" vt="8">GEIIS_IW</p>
<p name="007510A1" vt="8">1</p>
</ps>
</p>
<p name="0040A010" vt="8">CONTAINS</p>
<p name="0040A040" vt="8">IMAGE</p>
</ps>
</Root>
Upvotes: 1