Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way I can specify defaultValue for collections with JAXB?

Tags:

java

xml

jaxb

xsd

xjc

I have this attribute in Java

@XmlList
@XmlElement(defaultValue = "COMMENTS CASE_INSENSITIVE")
protected List<RegexFlag> regexFlags;

Which has been generated from XJC, originating from this XSD:

<element name="regexFlags" type="tns:RegexFlags" 
    minOccurs="0" maxOccurs="1" default="COMMENTS CASE_INSENSITIVE"/>

<simpleType name="RegexFlags">
  <list itemType="tns:RegexFlag"/>
</simpleType>

<simpleType name="RegexFlag">
  <restriction base="string">
    <enumeration value="UNIX_LINES"/>
    <enumeration value="CASE_INSENSITIVE"/>
    <enumeration value="COMMENTS"/>
    <enumeration value="MULTILINE"/>
    <enumeration value="LITERAL"/>
    <enumeration value="DOTALL"/>
    <enumeration value="UNICODE_CASE"/>
    <enumeration value="CANON_EQ"/>
    <enumeration value="UNICODE_CHARACTER_CLASS"/>
  </restriction>
</simpleType>

Unfortunately, this doesn't seem to work. The default values are not unmarshalled correctly. The value I get when I don't have a <regexFlags/> element is really just an empty list. What am I doing wrong? Is this even possible with JAXB?

like image 772
Lukas Eder Avatar asked Nov 10 '22 21:11

Lukas Eder


1 Answers

The defaultValue property on the @XmlElement annotation, is what a JAXB (JSR-222) implementation should swap in for the value of an empty element. Their appears to be a bug in the reference and MOXy implementations of this when that element is mapped to a property annotated with @XmlList.

Domain Model

Root

Here is a sample class with 3 String and 3 List<String> fields all annotated with @XmlElement(defaultValue="a b c"). The List<String> fields are also annotated with @XmlList.

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlElement(defaultValue="a b c")
    String singleMissingElement;

    @XmlElement(defaultValue="a b c")
    String singleEmptyElement;

    @XmlElement(defaultValue="a b c")
    String singlePopulatedElement;

    @XmlElement(defaultValue="a b c")
    @XmlList
    List<String> listMissingElement;

    @XmlElement(defaultValue="a b c")
    @XmlList
    List<String> listEmptyElement;

    @XmlElement(defaultValue="a b c")
    @XmlList
    List<String> listPopulatedElement;

}

Demo Code

Demo

Below is some demo code that unmarshals some XML and outputs the resulting fields from the object. The XML elements are populated based on the fields name (i.e. missing means absent from the XML, empty means empty element, and populated means element with a value).

import java.io.StringReader;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        StringReader xml = new StringReader("<root><singleEmptyElement/><singlePopulatedElement>populated</singlePopulatedElement><listEmptyElement/><listPopulatedElement>populated</listPopulatedElement></root>");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(xml);

        System.out.println(root.singleMissingElement);
        System.out.println(root.singleEmptyElement);
        System.out.println(root.singlePopulatedElement);

        System.out.println(root.listMissingElement);
        System.out.println(root.listEmptyElement);
        System.out.println(root.listPopulatedElement);
    }

}

Output

The only value that comes out unexpectedly is the 5th one which corresponds to the empty element for the List<String> field. Based on the defaultValue I would have expected it to be a List that contained the strings a, b, and c.

null
a b c
populated
null
[]
[populated]

Why is this the Behaviour for defaultValue on @XmlElement?

XML Schema (schema.xsd)

The defaultValue property on the @XmlElement annotation corresponds to the default property on an element declaration in an XML Schema. Below is the schema equivalent of what we have annotated in our Java model.

<?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="root" type="root"/>

  <xs:complexType name="root">
    <xs:sequence>
      <xs:element name="singleMissingElement" type="xs:string" default="a b c" minOccurs="0"/>
      <xs:element name="singleEmptyElement" type="xs:string" default="a b c" minOccurs="0"/>
      <xs:element name="singlePopulatedElement" type="xs:string" default="a b c" minOccurs="0"/>
      <xs:element name="listMissingElement" minOccurs="0" default="a b c">
        <xs:simpleType>
          <xs:list itemType="xs:string"/>
        </xs:simpleType>
      </xs:element>
      <xs:element name="listEmptyElement" minOccurs="0" default="a b c">
        <xs:simpleType>
          <xs:list itemType="xs:string"/>
        </xs:simpleType>
      </xs:element>
      <xs:element name="listPopulatedElement" minOccurs="0" default="a b c">
        <xs:simpleType>
          <xs:list itemType="xs:string"/>
        </xs:simpleType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Demo Code

Below is some code that will do a SAX parse with schema validation enabled where the ContentHandler dumps some info to System.out.

import java.io.*;
import javax.xml.XMLConstants;
import javax.xml.parsers.*;
import javax.xml.validation.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

public class ParseDemo {

    public static void main(String[] args) throws Exception {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = sf.newSchema(new File("src/forum27528698/schema.xsd")); 

        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setSchema(schema);
        SAXParser sp = spf.newSAXParser();
        XMLReader xr = sp.getXMLReader();
        xr.setContentHandler(new MyHandler());

        StringReader xml = new StringReader("<root><singleEmptyElement/><singlePopulatedElement>populated</singlePopulatedElement><listEmptyElement/><listPopulatedElement>populated</listPopulatedElement></root>");
        InputSource input = new InputSource(xml);
        xr.parse(input);
    }

    private static class MyHandler extends DefaultHandler {

        @Override
        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
            System.out.print("<" + qName + ">");
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            System.out.print(new String(ch, start, length));
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            System.out.println("</" + qName + ">");
        }

    }

}

Output

In the output we see that the default value has been applied to the empty elements, but not the absent ones.

<root>
<singleEmptyElement>a b c</singleEmptyElement>
<singlePopulatedElement>populated</singlePopulatedElement>
<listEmptyElement>a b c</listEmptyElement>
<listPopulatedElement>populated</listPopulatedElement>
</root>
like image 120
bdoughan Avatar answered Nov 14 '22 21:11

bdoughan