Элемент 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"/>
Если это правильно, попробуйте запустить тест с вашим классом.
Ответ 3
Вы можете настроить привязку
<jaxb:globalBindings generateElementProperty="false" />
Как описано в Индивидуальная привязка, для того же точного случая, о котором вы спрашиваете.
Я использую пользовательскую привязку с плагином maven org.jvnet.jaxb2.maven2: maven-jaxb2-plugin