JAXB element that is both optional and nillable

后端 未结 3 905
有刺的猬
有刺的猬 2020-12-05 11:48

I have re-formatted the question to hopefully make my intentions clearer.

Architecture
I\'m writing some web services that I will be publishing

相关标签:
3条回答
  • 2020-12-05 12:29

    As I understand you Ben the following XSD:

    <xs:element name="myElement" type="xs:string" nillable="true" minOccurs="0" /> 
    

    Should result in:

    @XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class, required = false)
    protected JAXBElement<String> myElement;
    

    Right?

    But for default JAXB implementation it is not the case. Looks like a bug in JAXB. I didn't find it in JAXB issue tracker. required attribute was introduced to @XmlElementRef in JAXB 2.2 in around 2009 but apparently no one created issue for this problem.

    required attribute cannot be changed using Binding Customizations.

    In this situation you can:

    • write your own plugin for XJC to add missing attribute to @XmlElementRef annotation. It is not that difficult. More information here.
    • use alternative JAXB implementation (MOXy works fine - required = false is generated using MOXy JAXB compiler)
    • wait for Oracle implementation of JAXB to be fixed.

    No matter which option you choose please raise issue in JAXB issue tracker so that the problem will be fixed.

    EDIT:

    To show that creating plugin is easy I created one. You can find it in my github repository. Feel free to use/copy/modify at will. I don't give guarantee that it works in 100% but for simple cases works like a charm.

    EDIT2:

    If schema generated based on java objects and JAXB annotations does not match your interface then you can use @WebService.wsdlLocation to point to your original, correct WSDL and XSD files.

    EDIT3:

    It's weird that nil is ignored by JAXB in your case. I ran a test using JAXB 2.2.6 and 2.2.7 and nil is correctly recognized:

    JAXBContext context = JAXBContext.newInstance(SomeElement.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();
    String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><ns2:someElement xmlns:ns2=\"http://www.example.org/example/\"><myElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/></ns2:someElement>";
    SomeElement someElement = (SomeElement) unmarshaller
            .unmarshal(new StringReader(xml));
    assertThat(someElement.getMyElement().isNil(), is(true));
    

    Could you check whether you correctly set nil attribute, e.g.:

    <myElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
    

    If it is correct please try to run the test with your class.

    0 讨论(0)
  • 2020-12-05 12:36

    You can customise the binding by

    <jaxb:globalBindings generateElementProperty="false" />
    

    As documented in Customized Binding , for the same exact case you are asking about.

    I'm using custom binding with maven plugin org.jvnet.jaxb2.maven2:maven-jaxb2-plugin

    0 讨论(0)
  • 2020-12-05 12:46

    TL;DR

    For

    @XmlElementRef(name="foo", required=false)
    protected JAXBElement<String> foo;
    

    An absent node in the document will correspond to this field being null. An XML element present in the document with xsi:nil="true" will correspond to the value being an instance of JAXBElement with a value of null.

    You can also provide an XML schema instead of having JAXB generate one using the location property on the package level @XmlSchema annotation.

    @XmlSchema(
        ...
        location="http://www.example.com/schema/root.xsd")
    package forum19665550;
    
    import javax.xml.bind.annotation.XmlSchema;
    

    Marshal/Unmarshal

    Java Model

    Root

    This is an object with two fields that can represent optional and nillable data.

    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.annotation.*;
    
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Root {
    
        @XmlElementRef(name="foo", required=false)
        protected JAXBElement<String> foo;
    
        @XmlElementRef(name="bar", required=false)
        protected JAXBElement<String> bar;
    
    }
    

    ObjectFactory

    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.annotation.*;
    import javax.xml.namespace.QName;
    
    @XmlRegistry
    public class ObjectFactory {
    
        @XmlElementDecl(name="foo")
        public JAXBElement<String> createFoo(String foo) {
            return new JAXBElement<String>(new QName("foo"), String.class, foo);
        }
    
        @XmlElementDecl(name="bar")
        public JAXBElement<String> createBar(String bar) {
            return new JAXBElement<String>(new QName("bar"), String.class, bar);
        }
    
    }
    

    Demo Code

    Demo

    The demo code below will investigate the differences in the values for foo and bar. You can use the JAXBIntrospector class to get the real value for an instance of JAXBElement. There is a bug in EclipseLink JAXB (MOXy) related to unmarshalling an instance of JAXBElement wrapping a null value (see: http://bugs.eclipse.org/420746).

    import java.io.File;
    import javax.xml.bind.*;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Root.class, ObjectFactory.class);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            File xml = new File("src/forum19665550/input.xml");
            Root root = (Root) unmarshaller.unmarshal(xml);
    
            System.out.println("foo was set:          " + (root.foo != null));
            System.out.println("bar was set:          " + (root.bar != null));
            System.out.println("foo value:            " + root.foo);
            System.out.println("bar value:            " + root.bar);
            System.out.println("foo unwrapped value:  " + JAXBIntrospector.getValue(root.foo));
            System.out.println("bar unwrapped value:  " + JAXBIntrospector.getValue(root.bar));
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(root, System.out);
        }
    
    }
    

    input.xml/Output

    In the resulting output we see that we can differentiate between an element being absent from the document and an element with `xsi:nil="true" and still have the resulting value be null.

    foo was set:          false
    bar was set:          true
    foo value:            null
    bar value:            javax.xml.bind.JAXBElement@4af42ea0
    foo unwrapped value:  null
    bar unwrapped value:  null
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
    

    Generating an XML Schema

    Demo Code

    GenerateSchema

    Below is some JAXB code that will generate an XML Schema from the annotated model.

    import java.io.IOException;
    import javax.xml.bind.*;
    import javax.xml.transform.Result;
    import javax.xml.transform.stream.StreamResult;
    
    public class GenerateSchema {
    
        public static void main(String[] args) throws Exception {
            JAXBContext jc = JAXBContext.newInstance(Root.class);
    
            jc.generateSchema(new SchemaOutputResolver() {
    
                @Override
                public Result createOutput(String namespaceUri,
                        String suggestedFileName) throws IOException {
                    StreamResult result = new StreamResult(System.out);
                    result.setSystemId(suggestedFileName);
                    return result;
                }
    
            });
        }
    
    }
    

    Output

    Here is the resulting XML Schema. You are correct that it doesn't indicate that the foo and bar elements are nillable.

    <?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="bar" type="xs:string"/>
    
      <xs:element name="foo" type="xs:string"/>
    
      <xs:element name="root" type="root"/>
    
      <xs:complexType name="root">
        <xs:sequence>
          <xs:element ref="foo" minOccurs="0"/>
          <xs:element ref="bar" minOccurs="0"/>
        </xs:sequence>
      </xs:complexType>
    </xs:schema>
    

    Providing an XML Schema

    Instead of having JAXB derive an XML Schema from your model, you can point to your existing one that will contain more information.

    package-info

    This is done by specifying the location property on the package level @XmlSchema annotation.

    @XmlSchema(
        ...
        location="http://www.example.com/schema/root.xsd")
    package forum19665550;
    
    import javax.xml.bind.annotation.XmlSchema;
    
    0 讨论(0)
提交回复
热议问题