Is there a way to specify defaultValue for collections with JAXB?

I have this attribute in Java

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

which was created from XJC, based on 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 does not work. Default values ​​are not correctly sorted. The value that I get when I don't have the <regexFlags/> element is actually just an empty list. What am I doing wrong? Is this possible with JAXB?

+5
source share
2 answers

The defaultValue property in the defaultValue annotation is that the JAXB implementation (JSR-222) should be replaced with the value of the empty element. It seems that they are an error in the reference and MOXy implementations of this when this 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 using @XmlElement(defaultValue="abc") . List<String> fields are also annotated using @XmlList .

 import java.util.List; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Root { @XmlElement(defaultValue="abc") String singleMissingElement; @XmlElement(defaultValue="abc") String singleEmptyElement; @XmlElement(defaultValue="abc") String singlePopulatedElement; @XmlElement(defaultValue="abc") @XmlList List<String> listMissingElement; @XmlElement(defaultValue="abc") @XmlList List<String> listEmptyElement; @XmlElement(defaultValue="abc") @XmlList List<String> listPopulatedElement; } 

Demo code

Demo

Below is some demo code that cancels some XML code and displays the resulting fields from the object. XML elements are populated based on the name of the fields (i.e., missing means that XML is missing, empty means an empty element, and populated means an 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); } } 

Exit

The only value that appears unexpectedly is the fifth, which corresponds to an empty element for the List<String> field. Based on defaultValue , I expected this to be a List containing the lines a , b and c .

 null abc populated null [] [populated] 

Why is this behavior for defaultValue on @XmlElement ?

XML Schema (schema.xsd)

The defaultValue property in the defaultValue annotation corresponds to the default property in the element declaration in the XML schema. Below is a schema equivalent to what we 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="abc" minOccurs="0"/> <xs:element name="singleEmptyElement" type="xs:string" default="abc" minOccurs="0"/> <xs:element name="singlePopulatedElement" type="xs:string" default="abc" minOccurs="0"/> <xs:element name="listMissingElement" minOccurs="0" default="abc"> <xs:simpleType> <xs:list itemType="xs:string"/> </xs:simpleType> </xs:element> <xs:element name="listEmptyElement" minOccurs="0" default="abc"> <xs:simpleType> <xs:list itemType="xs:string"/> </xs:simpleType> </xs:element> <xs:element name="listPopulatedElement" minOccurs="0" default="abc"> <xs:simpleType> <xs:list itemType="xs:string"/> </xs:simpleType> </xs:element> </xs:sequence> </xs:complexType> </xs:schema> 

Demo code

Below is the code that will run the SAX analysis with a schema check, where the ContentHandler dumps some information into 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 + ">"); } } } 

Exit

In the output, we see that the default value was applied to empty elements, but not to missing ones.

 <root> <singleEmptyElement>ab c</singleEmptyElement> <singlePopulatedElement>populated</singlePopulatedElement> <listEmptyElement>ab c</listEmptyElement> <listPopulatedElement>populated</listPopulatedElement> </root> 
+1
source

There seems to be a misunderstanding of JAXB's idea of defaultValue in the original question (or, frankly, just a mistake). This code snippet explains this:

 import static java.lang.System.out; import static javax.xml.bind.JAXB.unmarshal; import java.io.StringReader; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlList; import javax.xml.bind.annotation.XmlRootElement; public class Test { public static void main(String[] args) { out.println(unmarshal(new StringReader("<x></x>"), X.class)); out.println(unmarshal(new StringReader("<x><flags/></x>"), X.class)); out.println(unmarshal(new StringReader("<x><flags>X Y</flags></x>"), X.class)); } } @XmlRootElement class X { @XmlList @XmlElement(defaultValue = "AB") protected List<String> flags; @Override public String toString() { return "X [flags=" + flags + "]"; } } 

Above result

 X [flags=null] X [flags=[A, B]] X [flags=[X, Y]] 

So, apparently, the presence of the corresponding element is important. If it is absent, defaultValue not applied. If it is present but the value is absent, defaultValue is defaultValue .

Bypass

The following workaround can be used to enforce these defaultValues :

 public class Test { public static void main(String[] args) { X x1 = unmarshal(new StringReader("<x></x>"), X.class); X x2 = unmarshal(new StringReader("<x><flags/></x>"), X.class); X x3 = unmarshal(new StringReader("<x><flags>X Y</flags></x>"), X.class); out.println("First unmarshal:"); out.println(x1); out.println(x2); out.println(x3); // Marshal the xml again. This will add the <flags/> element StringWriter s1 = new StringWriter(); JAXB.marshal(x1, s1); StringWriter s2 = new StringWriter(); JAXB.marshal(x2, s2); StringWriter s3 = new StringWriter(); JAXB.marshal(x3, s3); // Now we're talking! x1 = unmarshal(new StringReader(s1.toString()), X.class); x2 = unmarshal(new StringReader(s2.toString()), X.class); x3 = unmarshal(new StringReader(s3.toString()), X.class); out.println(); out.println("Second unmarshal:"); out.println(x1); out.println(x2); out.println(x3); } } 

And annotations should be transferred from the attribute to getter with explicit initialization of the list in place (which is why it smells to me like an error).

 @XmlRootElement class X { protected List<String> flags; @XmlList @XmlElement(defaultValue = "AB") public List<String> getFlags() { if (flags == null) flags = new ArrayList<>(); return flags; } @Override public String toString() { return "X [flags=" + flags + "]"; } } 

Now, the result is now "correct" ...

 First unmarshal: X [flags=null] X [flags=[A, B]] X [flags=[X, Y]] Second unmarshal: X [flags=[A, B]] X [flags=[A, B]] X [flags=[X, Y]] 

All this reminds me ...

 System.gc(); System.gc(); // Just to be sure 

; -)

0
source

Source: https://habr.com/ru/post/1209368/


All Articles