web-dev-qa-db-ja.com

オプションであり、nillableであるJAXB要素

質問を再フォーマットして、意図がより明確になることを願っています。

アーキテクチャ
私は、JAX-WSを使用して自分で公開するWebサービスをいくつか作成しています。しばらくの間使用してきたプロセスは、最初に要求オブジェクトと応答オブジェクトのみを定義するスキーマを記述することです。これは、xmlメッセージの構造を承認するために顧客に送信されます。基本的なスキーマよりも複雑なので、私はwsdl全体を自分で書きたくありません。

次に、JAXBコマンドxjcを使用して、スキーマの要求および応答タイプに基づいてクラスを生成します。次に、このクラスをパラメーターとして使用し、JAX-WS注釈付きエンドポイントクラスの型を返します。

これにより、呼び出し可能なWebサービスが提供されます。これにより、送信および返される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;

これの効果は、要素がnillableまたはオプションにならないことです。 JAX-WSがwsdlの一部として書き込むスキーマは、要素を必須でnillableに設定していないため、スキーマ検証をオフにしても、オブジェクトにnilを渡すことができません。

試したこと
それを必須でnillableに変更すると、この生成されたコードを取得します。

@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アノテーションには、必須ではないように設定するための属性がありますが、フィールドをnull可能に設定するための属性はありません。

@XmlElementアノテーションには、必須とnull可能の両方の属性がありますが、これらは単にnullオブジェクトになるため、xmlに含まれていない要素と含まれているがnullの要素を区別する方法はありません。これが、JAXBElementとともに@XmlElementRefを使用する必要がある理由です。

バグには2つの問題が含まれていると思います。最初に、xjcコマンドはrequired = falseの要素を生成する必要があります。次に、要素がnull可能かどうかを設定するための属性が@XmlElementRefにあり、これも設定する必要があります。

誰かが修正/回避策を知っていますか?私はグーグルで試しましたが、答えなしで同じ質問をしている人々だけを見つけました。これは通常、それが不可能であることを意味します... TIA。

追加
私はjaxb 2.2.6を使用していますが、mavenプラグインはjaxb2-maven-plugin 1.5です。

11
Ben Thurley

TL; DR

For

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

ドキュメントにノードがない場合は、このフィールドがnullであることに対応します。 xsi:nil="true"を使用してドキュメントに存在するXML要素は、JAXBElementの値を持つnullのインスタンスである値に対応します。

パッケージレベルの@XmlSchemaアノテーションでlocationプロパティを使用してJAXBにスキーマを生成させる代わりに、XMLスキーマを提供することもできます。

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

import javax.xml.bind.annotation.XmlSchema;

マーシャル/アンマーシャル

Javaモデル

ルート

これは、オプションでnillableなデータを表すことができる2つのフィールドを持つオブジェクトです。

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のインスタンスを非整列化してnull値をラップすることに関連するバグがあります( 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

結果の出力では、ドキュメントに存在しない要素と `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

以下は、注釈付きモデルからXMLスキーマを生成するいくつかのJAXBコードです。

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スキーマです。 fooおよびbar要素が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>

XMLスキーマの提供

JAXBにモデルからXMLスキーマを派生させる代わりに、より多くの情報を含む既存のスキーマを指すことができます。

package-info

これは、パッケージレベルの@XmlSchemaアノテーションでlocationプロパティを指定することによって行われます。

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

import javax.xml.bind.annotation.XmlSchema;
13
bdoughan

私が理解しているように、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 では見つかりませんでした。 required属性は、2009年頃にJAXB 2.2の@XmlElementRefに導入されましたが、明らかにこの問題の問題を作成した人はいません。

required属性は、バインディングのカスタマイズを使用して変更できません。

この状況では、次のことができます。

  • xJC用の独自のプラグインを記述して、欠落している属性を@XmlElementRefアノテーションに追加します。それはそれほど難しいことではありません。詳細情報 こちら
  • 代替のJAXB実装を使用します(MOXyは正常に動作します-required = falseMOXy JAXBコンパイラを使用して生成されます
  • oracleによるJAXBの実装が修正されるのを待ちます。

どちらのオプションを選択する場合でも、問題が修正されるように、 JAXB Issue Tracker で問題を報告してください。

編集:

プラグインの作成が簡単であることを示すために、プラグインを作成しました。私の githubリポジトリ にあります。自由に使用/コピー/変更してください。 100%動作することを保証するものではありませんが、単純なケースでは魅力のように動作します。

EDIT2:

JavaオブジェクトおよびJAXBアノテーションに基づいて生成されたスキーマがインターフェースと一致しない場合は、@WebService.wsdlLocationを使用して、元の正しいWSDLおよびXSDファイルを指すことができます。

EDIT3:

あなたの場合、nilがJAXBによって無視されるのは奇妙です。 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"/>

正しい場合は、クラスでテストを実行してみてください。

2
Dawid Pytel

次の方法でバインディングをカスタマイズできます

<jaxb:globalBindings generateElementProperty="false" />

Customized Binding に記載されているように、あなたが求めているのとまったく同じケースについて。

Mavenプラグインでカスタムバインディングを使用しています org.jvnet.jaxb2.maven2:maven-jaxb2-plugin

2
M-Zaiady