Элемент JAXB, который является как дополнительным, так и безрадочным

Я переформатировал вопрос, надеюсь, сделать мои намерения более ясными.

Архитектура
Я пишу несколько веб-сервисов, которые я буду публиковать с помощью JAX-WS. Процесс, который мы использовали в течение некоторого времени, - это сначала написать схему, которая определяет только объекты запроса и ответа. Это отправляется клиенту для утверждения структуры сообщений xml. Я не хочу сам писать весь wsdl, поскольку он более сложный, чем базовая схема.

Далее я использую JAXB-команду xjc для генерации классов на основе типов запросов и ответов в моей схеме. Затем я использую эти классы как параметры и типы возвращаемых данных в классе аннотированных конечных точек JAX-WS.

Теперь это дает мне веб-сервис, который я могу позвонить. Это дает мне больше контроля за отправкой и возвратом xml, а также автоматизирует повторение, необходимое для написания полного wsdl.

Проблема
В схеме у меня есть такой элемент:

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

Поэтому я хочу различать пользовательский параметр null или blank. Сгенерированный класс имеет этот атрибут.

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

Эффект этого заключается в том, что элемент не становится ни полным, ни дополнительным. Схема, которую JAX-WS пишет как часть wsdl, установила обязательный элемент, а не nillable, и если я отключу проверку схемы, я все равно не могу передать ноль моему объекту.

Проверенные вещи
Если я изменю его, чтобы он был необходим и 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;

Теперь этот элемент становится необязательным, но я все равно не могу установить его на нуль. В результате получается JAXBElement со значением пустой строки. Это происходит только в том случае, если вы отключили проверку схемы, поскольку результирующая JSX-WS wsdl/schema не устанавливает элемент как nillable, поэтому его недействительный запрос.

Резюме
Я полагаю, что это ошибка с JAXB. Аннотация @XmlElementRef имеет атрибут, чтобы установить его как необязательный, но нет атрибута для установки поля как nullable.

В аннотации @XmlElement есть атрибуты как требуемые, так и нулевые, но они приводят только к нулевому объекту, поэтому не было способа различать элемент, не включенный в xml, или элемент, который был включен, но null. Вот почему вам нужно использовать @XmlElementRef вместе с JAXBElement.

Я думаю, что ошибка включает в себя две проблемы. Сначала команда xjc должна генерировать элемент с обязательным = false. Во-вторых, должен быть атрибут на @XmlElementRef, чтобы установить, является ли элемент нулевым, и это также должно быть установлено.

Кто-нибудь знает об исправлении/обходном пути? Я попробовал поиск в Интернете, но нашел людей, задающих один и тот же вопрос без ответа. Это обычно означает, что это невозможно... TIA.

Дополнительные
Я использую jaxb 2.2.6, а плагин maven - jaxb2-maven-plugin 1.5.

Ответ 1

TL; DR

Для

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

Отсутствующий node в документе будет соответствовать этому полю, равному null. Элемент XML, присутствующий в документе с xsi:nil="true", будет соответствовать значению, являющемуся экземпляром JAXBElement со значением null.

Вы также можете предоставить схему XML вместо того, чтобы JAXB генерировать один, используя свойство location в аннотации @XmlSchema уровня пакета.

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

import javax.xml.bind.annotation.XmlSchema;

Marshal/распаковать

Модель Java

Root

Это объект с двумя полями, которые могут представлять необязательные и nillable данные.

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

Демонстрационный код ниже рассмотрит различия в значениях для foo и bar. Вы можете использовать класс JAXBIntrospector, чтобы получить реальное значение для экземпляра JAXBElement. В EclipseLink JAXB (MOXy) есть ошибка, связанная с unmarshalling экземпляром 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:            [email protected]
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-схему из аннотированной модели.

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-схему из вашей модели, вы можете указать свой существующий, который будет содержать больше информации.

пакет-инфо

Это делается путем указания свойства location на аннотации пакета @XmlSchema.

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

import javax.xml.bind.annotation.XmlSchema;

Ответ 2

Как я понимаю, вы Бен следующий 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;

Right?

Но для реализации по умолчанию JAXB это не так. Похож на ошибку в JAXB. Я не нашел его в JAXB вопросник. Атрибут required был введен в @XmlElementRef в JAXB 2.2 примерно в 2009 году, но, по-видимому, никто не создал проблему для этой проблемы.

Атрибут

required не может быть изменен с помощью настроек привязки.

В этой ситуации вы можете:

  • напишите свой собственный плагин для XJC, чтобы добавить отсутствующий атрибут в аннотацию @XmlElementRef. Это не так сложно. Подробнее здесь.
  • используйте альтернативную реализацию JAXB (MOXy работает отлично - required = false создается с использованием MOXy JAXB-компилятор)
  • подождите, пока будет исправлена ​​реализация Oracle JAXB.

Независимо от того, какой вариант вы выберете, поднимите вопрос в отслеживатель JAXB, чтобы проблема была исправлена.

EDIT:

Чтобы показать, что создание плагина легко, я создал его. Вы можете найти его в моем репозитории github. Не стесняйтесь использовать/копировать/изменять по своему усмотрению. Я не гарантирую, что он работает на 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"/>

Если это правильно, попробуйте запустить тест с вашим классом.