Файл привязки JAXB: XmlAdapters и имя пакета

У меня есть файл привязки, подобный этому

<jxb:bindings version="2.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <jxb:bindings schemaLocation="example.xsd" node="/xs:schema">
    <jxb:schemaBindings>
        <jxb:package name="example" />
    </jxb:schemaBindings>
    <jxb:globalBindings>
        <jxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
            parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
            printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />
        <jxb:javaType name="java.util.Calendar" xmlType="xs:date"
            parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
            printMethod="javax.xml.bind.DatatypeConverter.printDate" />
        <jxb:javaType name="java.util.Calendar" xmlType="xs:time"
            parseMethod="javax.xml.bind.DatatypeConverter.parseTime"
            printMethod="javax.xml.bind.DatatypeConverter.printTime" />
    </jxb:globalBindings>

  </jxb:bindings>
</jxb:bindings>

Класс схемы генерируется в "примере" (правильно), но XmlAdapters в "org.w3._2001.xmlschema" (неверно). Как я могу это исправить?

Ответ 1

Я имел эту проблему, а также, решить ее с помощью этого.

Основная предпосылка заключается в том, что вы включаете в компиляцию XJC схему со следующим содержимым:

<schema xmlns="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://www.w3.org/2001/XMLSchema"
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
  jaxb:version="2.0">
  <annotation><appinfo>
    <jaxb:schemaBindings>
      <jaxb:package name="org.acme.foo"/>
    </jaxb:schemaBindings>
  </appinfo></annotation>
</schema>

Затем вы настраиваете имя пакета в том месте, где вы хотите разместить сгенерированные адаптеры. XJC будет полагать, что эта схема является частью схемы, заданной для самой XML-схемы W3C, и будет соблюдать привязки в ней.

Ответ 2

Пакет org.w3._2001.xmlschema создается здесь, потому что XJC должен генерировать класс, который расширяет javax.xml.bind.annotation.adapters.XmlAdapter, что в свою очередь вызывает ваши статические методы синтаксического анализа/печати. По какой-то причине он помещает их в этот пакет, а не куда полезнее.

Вы не сказали, какую реализацию JAXB вы используете, но JAXB RI имеет расширение для привязки привязки javaType, которое позволяет вам напрямую указать подкласс XmlAdapter, а не parseMethod/printMethod пар. Это устраняет необходимость создания синтетического класса моста XmlAdapter. Для этого выполните RI docs.

Я предполагаю, что EclipseLink/Moxy имеет нечто похожее на это, но я не уверен, что XJC, который поставляется с Java6, способен на это (Sun, похоже, удалил половину полезного материала из RI, когда они привели его в JRE).

Ответ 3

Для пользователей Apache CXF самым чистым способом является использование опции -p, предлагаемой wsdl2java.

-p [wsdl-namespace =] PackageName

Указывает нулевые или более имена пакетов, которые будут использоваться для сгенерированного кода. Опционально задает пространство имен WSDL для сопоставления имен пакетов.

В нашем случае

-p http://www.w3.org/2001/XMLSchema=org.acme.foo

Если вы используете cxf-codegen-plugin, просто добавьте еще пару <extraarg>.

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>${cxf.version}</version>
        [...]
    <extraarg>-p</extraarg>
    <extraarg>http://www.w3.org/2001/XMLSchema=org.acme.foo</extraarg>
        [...]
</plugin>

Нет необходимости в целевом пространстве имен, указывающем на зарезервированное пространство имен XSD и не требуется привязка пакета jaxb для всех.

Ответ 4

Лучший способ использования GlobalBinding - указать явный адаптер вместо использования этой пары синтаксического анализа/печати. Например, вместо следующего:

<jaxb:javaType name="java.lang.Long" xmlType="xs:long"
                      parseMethod="com.mypackage.myclass.parseLong"
                  printMethod="com.mypackage.myclass.print"/>

Вместо этого вы должны:

<xjc:javaType name="java.lang.Long" xmlType="xs:long"
                  adapter="com.mypackage.LongAdapter"/>

Не забудьте добавить пространство имен для xjc:

xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
          jxb:extensionBindingPrefixes="xjc"

Класс LongAdapter будет выглядеть следующим образом:

public class LongAdapter
extends XmlAdapter<String, Long>
{


public Long unmarshal(String value) {
    return your_util_class.parseLong(value);
}

public String marshal(Long value) {
    return your_util_class.print(value);
}

}

Таким образом, поскольку вы явно указали классы адаптеров, jaxb не будет генерировать адаптеры по умолчанию с именем пакета по умолчанию org.w3._2001.xmlschema.

Очень важно избегать использования имени пакета по умолчанию org.w3._2001.xmlschema. Например, если у вас есть один проект A и один проект B, и у обоих из них есть схема и привязки. По-старому они генерируют адаптеры с точно такими же полными именами, например. org.w3._2001.xmlschema.Adapter1. Однако этот адаптер может быть для Long в проекте A и для Integer в проекте B. Тогда, допустим, у вас есть нисходящий проект C, использующий как A, так и B. Теперь проблема становится неприятной. Если C необходимо использовать Adapter1, вы не можете предсказать, что использованный используется от A для Long или от B для Integer. Тогда ваше приложение C может работать нормально через какое-то время, но, возможно, в некоторых ситуациях может возникнуть странным образом. Если это произойдет, исключение типа будет выглядеть следующим образом:

org.w3._2001.xmlschema.Adapter1 is not applicable to the field type java.lang.Double...

Решение, упомянутое Роем Труелове, кажется, не работает, когда я попробовал его в своей среде с maven-jaxb2-plugin, даже если теория верна.

Ответ 5

Используйте встроенные конвертеры для распространенных типов данных.

<jxb:javaType name="java.lang.Integer" xmlType="xs:integer"              
parseMethod="javax.xml.bind.DatatypeConverter.parseInt"                  
printMethod="javax.xml.bind.DatatypeConverter.printInt" />