JAXB元素既可选又可以为nillable

时间:2013-10-29 17:48:04

标签: java xml jaxb jax-ws

我重新格式化了这个问题,希望能让我的意图更清晰。

建筑
我正在编写一些我将使用JAX-WS自己发布的Web服务。我们已经使用了一段时间的过程是首先编写一个仅定义请求和响应对象的模式。这将被发送给客户以批准xml消息的结构。我不想自己编写整个wsdl,因为它比基本模式更复杂。

接下来,我使用JAXB命令xjc根据模式中的请求和响应类型生成类。然后,我将此类用作参数,并在JAX-WS带注释的端点类上返回类型。

这给了我一个我可以打电话的网络服务。它使我能够更好地控制发送和返回的xml,但也可以自动完成写入完整wsdl所需的重复。

问题
在模式中,我有一个这样的元素:

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

所以我想区分用户设置null或空白。然后生成的类具有此属性。

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

这样做的结果是元素既不​​是可填充的也不是可选的。 JAX-WS作为wsdl的一部分写入的模式已将元素设置为必需而不是nillable,如果我关闭模式验证,我仍然无法将nil传递给我的对象。

尝试过的事情
如果我将其更改为必需且可以为零,那么我将获得此生成的代码。

@XmlElement(required = true, nillable = true)
protected String myElement;

如果我将其更改为可选而不是nillable,那么我将获得此生成的代码。

protected String myElement

因此,如果您使用JAXB,则可以使用或不是两者。完全令人失望!

我也尝试手动将生成的类更改为这样。

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

这现在使元素可选,但我仍然无法将其设置为nil。这样做会导致JAXBElement的值为空字符串。只有当你关闭模式验证时才会这样做,因为生成的JAX-WS wsdl / schema没有将元素设置为nillable,所以它不是有效的请求。

摘要
我相信这是JAXB的一个错误。 @XmlElementRef注释具有将其设置为不需要的属性,但没有将该字段设置为可空的属性。

@XmlElement注释具有必需和可空的属性,但这些只会导致空对象,因此无法区分未包含在xml中的元素或包含但为null的元素。这就是您需要将@XmlElementRef与JAXBElement一起使用的原因。

我认为这个错误包含两个问题。首先,xjc命令应生成required = false的元素。其次,@ xmlElementRef应该有一个属性来设置元素是否可以为空,这也应该设置。

有没有人知道修复/解决方法?我试过谷歌搜索但只发现人们在没有答案的情况下问同样的问题。这通常意味着它不可能...... TIA。

其他
我使用的是jaxb 2.2.6,而maven插件是jaxb2-maven-plugin 1.5。

2 个答案:

答案 0 :(得分:13)

TL; DR

有关

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

文档中缺少的节点将对应于此字段为空。文档中包含xsi:nil="true"的XML元素将对应于JAXBElement的实例值,其值为null

您还可以提供XML架构,而不是让JAXB使用包级别location注释上的@XmlSchema属性生成一个。

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

import javax.xml.bind.annotation.XmlSchema;

元帅/解组

Java模型

<强>根

这是一个包含两个字段的对象,可以表示可选和可存档的数据。

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);
    }

}

演示代码

<强>演示

下面的演示代码将调查foobar值的差异。您可以使用JAXBIntrospector类来获取JAXBElement实例的实际值。 EclipseLink JAXB(MOXy)中存在一个与解组包含空值的JAXBElement实例相关的错误(请参阅: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中/输出

在结果输出中,我们看到我们可以区分文档中缺少的元素和“xsi:nil =”true“的元素,并且结果值仍为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"/>

生成XML架构

演示代码

<强> GenerateSchema

下面是一些JAXB代码,它将从带注释的模型生成XML Schema。

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;
            }

        });
    }

}

<强>输出

这是生成的XML Schema。您是正确的,它并不表示foobar元素是可以为空的。

<?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>

提供XML架构

您可以指向包含更多信息的现有XML Schema,而不是让JAXB从您的模型中派生XML Schema。

<强>包信息

这是通过在包级别location注释上指定@XmlSchema属性来完成的。

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

import javax.xml.bind.annotation.XmlSchema;

答案 1 :(得分:2)

据我所知,Ben以下XSD:

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

应该导致:

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

右?

但是对于默认的JAXB实现,情况并非如此。看起来像JAXB中的错误。我没有在JAXB issue tracker中找到它。在2009年左右,JAXB 2.2中required引入了@XmlElementRef属性,但显然没有人为此问题创建问题。

使用绑定自定义无法更改

required属性。

在这种情况下,你可以:

  • 为XJC编写自己的插件,为@XmlElementRef注释添加缺少的属性。这并不困难。更多信息here
  • 使用替代JAXB实现(MOXy工作正常 - 使用MOXy JAXB compiler生成required = false
  • 等待修复JAXB的Oracle实现。

无论您选择哪个选项,请在JAXB issue tracker中提出问题,以便解决问题。

修改

为了表明创建插件很简单,我创建了一个。你可以在github repository找到它。随意使用/复制/修改。我不保证它100%有效,但简单的情况就像魅力一样。

<强> EDIT2:

如果基于java对象和JAXB注释生成的模式与您的接口不匹配,那么您可以使用@WebService.wsdlLocation指向原始的,正确的WSDL和XSD文件。

<强> EDIT3:

在您的情况下,JAXB会忽略nil这很奇怪。我使用JAXB 2.2.6和2.2.7进行了测试,并且nil被正确识别:

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));

您可以检查是否正确设置了nil属性,例如:

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

如果正确,请尝试与您的班级一起进行测试。