Как разобрать вложенные статические классы-оболочки, созданные для дочерних коллекций с помощью WSImport

Я пишу веб-службу на основе SOAP и клиентское приложение, которое вызывает веб-службу с данными импорта, считанными из файла XML. Я использовал wsimport для создания необходимых классов для клиента из файла WSDL.

Файл импорта «Books.xml» должен выглядеть так:

<?xml version="1.0" encoding="UTF-8"?>
<books>
    <book isbn="aaa" title="Aaa" subtitle="aaa" description="aaa" pages="25" language="german">
         <publisher name="a" postcode="a" countrycode="a" />
         <authors>
             <author firstname="a" lastname="a" birthday="1999-01-01" />
         </authors>
    </book>
</books>

Я использую созданный самостоятельно класс-оболочку Books.java в качестве @XmlRootElement для разупорядочения списка книг. При распаковке XML-файла я не могу получить дочерние элементы элемента «authors», поэтому объект Book, созданный демаршаллером, никогда не имеет авторов.

Это код, используемый для распаковки XML-файла и после этого рассортировки результирующего объекта Books:

JAXBContext jaxbContext = JAXBContext.newInstance(Books.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();

Source source = new StreamSource(new File(xmlPath));
JAXBElement<Books> jaxbBooks = unmarshaller.unmarshal(source, Books.class);
Books books = jaxbBooks.getValue();

Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "ISO-8859-1");
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(books, System.out); // just for debug purposes

что дает следующий результат:

<books xmlns:ns2="http://technikumwien.at/">
    <book isbn="aaa" title="Aaa" subtitle="aaa" description="aaa" pages="25" language="german">
        <ns2:publisher publisherId="0" name="a" postcode="a" countrycode="a"/>
        <ns2:authors/>
    </book>
</books>

В моем сервере Book.java я использовал @XmlElementWrapper для переноса списка авторов:

@XmlElementWrapper(name="authors")
@XmlElement(name="author")
private List<Author> authors;

Я взглянул на клиентский Book.java, сгенерированный wsimport — список авторов был преобразован во вложенный статический класс:

/**
 * <p>Java-Klasse für anonymous complex type.
 * 
 * <p>Das folgende Schemafragment gibt den erwarteten Content an, der in dieser Klasse enthalten ist.
 * 
 * <pre>
 * &lt;complexType>
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element ref="{http://technikumwien.at/}author" maxOccurs="unbounded" minOccurs="0"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "author"
})
public static class Authors {

    protected List<Author> author;

    /**
     * Gets the value of the author property.
     * 
     * <p>
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the JAXB object.
     * This is why there is not a <CODE>set</CODE> method for the author property.
     * 
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getAuthor().add(newItem);
     * </pre>
     * 
     * 
     * <p>
     * Objects of the following type(s) are allowed in the list
     * {@link Author }
     * 
     * 
     */
    public List<Author> getAuthor() {
        if (author == null) {
            author = new ArrayList<Author>();
        }
        return this.author;
    }

}

Я заметил, что атрибут name аннотации @XmlType был создан пустым, и если я заменю @XmlType(name="") на @XmlType(name="authors"), десортировка преуспеет в захвате дочерних элементов автора книги:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<books xmlns:ns2="http://technikumwien.at/">
    <book isbn="aaa" title="Aaa" subtitle="aaa" description="aaa" pages="25" language="german">
        <ns2:publisher publisherId="0" name="a" postcode="a" countrycode="a"/>
        <ns2:authors>
            <ns2:author authorId="0" firstname="a" lastname="a" birthday="1999-01-01"/>
        </ns2:authors>
    </book>
</books>

ОДНАКО Я НЕ ХОЧУ ИЗМЕНЯТЬ КЛАССЫ, СОЗДАВАЕМЫЕ WSIMPORT!

Итак, я решил проблему, разобрав XML в дерево DOM, пройдя по дереву DOM с помощью TreeWalker. Для каждого узла, если узел был элементом «Книга», я разбирал этот «узел книги» и помещал его в отдельный список книг. Если пройденный Node был элементом ‹Author›, я разбирал «Author Node» и добавлял объект Author к последнему объекту Book в списке Books:

private static final List<Book> traverseLevel(TreeWalker walker, Unmarshaller unmarshaller, List<Book> bookList) throws JAXBException {
    Node parent = walker.getCurrentNode();

    if (parent.getNodeName().equals("book"))
    {
        JAXBElement<Book> bookElem = unmarshaller.unmarshal(parent, Book.class);
        Book book = bookElem.getValue();
        bookList.add(book);
    }

    if (parent.getNodeName().equals("author"))
    {
        JAXBElement<Author> authorElem = unmarshaller.unmarshal(parent, Author.class);
        Author author = authorElem.getValue();
        bookList.get(bookList.size() - 1).getAuthors().getAuthor().add(author);
    }

    for (Node n = walker.firstChild(); n != null; n = walker.nextSibling()) {
        traverseLevel(walker, unmarshaller, bookList);
    }
    walker.setCurrentNode(parent);

    return  bookList;
}

Это решение работает, однако я нахожу его ужасно негибким, и я думаю, что должен быть более простой способ успешно разобрать вложенные элементы Author Книг без ручного обхода дерева DOM.

Мне также пришлось использовать следующее исправление https://stackoverflow.com/a/7736235/4716861, чтобы избавиться от проблема с префиксом пространства имен, поэтому в package-info.java моего сервера я написал:

    @XmlSchema(
            namespace = "http://technikumwien.at/",
            elementFormDefault = XmlNsForm.QUALIFIED,
            xmlns = {
                    @XmlNs(prefix="", namespaceURI="http://technikumwien.at/")
            }
    )

    @XmlJavaTypeAdapters({
            @XmlJavaTypeAdapter(type=LocalDate.class, value=LocalDateXmlAdapter.class)
    })

    package at.technikumwien;
    import javax.xml.bind.annotation.*;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
    import java.time.LocalDate;

Часть файла Wsdl (создается автоматически при запуске WildFly):

<xs:complexType name="book">
  <xs:sequence>
    <xs:element minOccurs="0" ref="publisher"></xs:element>
    <xs:element minOccurs="0" name="authors">
      <xs:complexType>
        <xs:sequence>
          <xs:element maxOccurs="unbounded" minOccurs="0" ref="author"></xs:element>
        </xs:sequence>
      </xs:complexType>
    </xs:element>
  </xs:sequence>
  <xs:attribute name="bookId" type="xs:long"></xs:attribute>
  <xs:attribute name="isbn" type="xs:string"></xs:attribute>
  <xs:attribute name="title" type="xs:string"></xs:attribute>
  <xs:attribute name="subtitle" type="xs:string"></xs:attribute>
  <xs:attribute name="description" type="xs:string"></xs:attribute>
  <xs:attribute name="pages" type="xs:int" use="required"></xs:attribute>
  <xs:attribute name="language" type="xs:string"></xs:attribute>
</xs:complexType>
<xs:complexType name="publisher">
  <xs:sequence></xs:sequence>
  <xs:attribute name="publisherId" type="xs:long" use="required"></xs:attribute>
  <xs:attribute name="name" type="xs:string"></xs:attribute>
  <xs:attribute name="postcode" type="xs:string"></xs:attribute>
  <xs:attribute name="countrycode" type="xs:string"></xs:attribute>
</xs:complexType>
<xs:complexType name="author">
  <xs:sequence></xs:sequence>
  <xs:attribute name="authorId" type="xs:long" use="required"></xs:attribute>
  <xs:attribute name="firstname" type="xs:string"></xs:attribute>
  <xs:attribute name="lastname" type="xs:string"></xs:attribute>
  <xs:attribute name="birthday" type="xs:string"></xs:attribute>
</xs:complexType>

сервер Book.java

@Entity
@Table(name="book")
@NamedQueries({
    @NamedQuery(name="Book.selectAll", query="SELECT b FROM Book b"),
    @NamedQuery(name="Book.selectByTitle", query="SELECT b FROM Book b WHERE b.title like CONCAT('%', :title ,'%')")
})
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @XmlAttribute(name="bookId")
    private Long bookId;
    @XmlAttribute(name="isbn")
    private String isbn;
    @XmlAttribute(name="title")
    private String title;
    @XmlAttribute(name="subtitle")
    private String subtitle;
    @XmlAttribute(name="description")
    private String description;
    @XmlAttribute(name="pages")
    private int pages;
    @XmlAttribute(name="language")
    private String language;
    @ManyToOne(optional = false)
    @NotNull
    @XmlElement(name="publisher")
    private Publisher publisher;
    @ManyToMany(mappedBy = "books", fetch = FetchType.EAGER)
    @NotEmpty
    @XmlElementWrapper(name="authors")
    @XmlElement(name="author")
    private List<Author> authors;

сервер Author.java

@Entity
@Table(name="author")
@NamedQueries({
        @NamedQuery(name="Author.selectAll", query="SELECT a FROM Author a"),
        @NamedQuery(name="Author.checkExists", query="SELECT a FROM Author a WHERE a.firstname = :firstname AND a.lastname = :lastname AND a.birthday = :birthday")
})
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @XmlAttribute(name="authorId")
    private long authorId;
    @XmlAttribute(name="firstname")
    private String firstname;
    @XmlAttribute(name="lastname")
    private String lastname;
    @XmlAttribute(name="birthday")
    @Convert(converter = LocalDatePersistenceConverter.class)
    private LocalDate birthday;
    @ManyToMany
    @XmlTransient
    private List<Book> books = new ArrayList<Book>();

Является ли проблема, что я испытываю ожидаемое поведение, или я что-то упускаю? Разве unmarshaller не должен иметь возможность захватить все дочерние элементы иерархии XML, используя классы, сгенерированные wsimport по умолчанию?

Извините за публикацию такого большого количества кода, но, возможно, это может иметь значение для решения проблемы. Надеюсь, кто-нибудь может намекнуть мне в правильном направлении. Спасибо!


person Florian    schedule 02.11.2015    source источник
comment
Из всего этого кода мне не удалось определить, используете ли вы один и тот же класс Book на сервере и клиенте. Если да, то проблем быть не должно. - В любом случае, если вы контролируете обе стороны: для чего вам элемент-обертка (<authors>)? Без него все будет хорошо.   -  person laune    schedule 02.11.2015
comment
на сервере я использую самописный класс Book.java, который является вторым кодовым блоком снизу, на клиенте я использую Book.java, сгенерированный wsimport. Мне нужно использовать оболочку ‹authors›, потому что мне не разрешено изменять формат моего импорта Books.xml. Да, я пробовал без оболочки, и это работает, но я ищу способ заставить ее работать, включая элемент оболочки.   -  person Florian    schedule 03.11.2015
comment
Я думаю, что самым элегантным решением было бы найти способ wsimport - автоматически сгенерировать вложенный класс Book.Authors с аннотацией @XmlType(name=authors) вместо @XmlType(name=), но я не знаю, возможно ли это вообще   -  person Florian    schedule 03.11.2015
comment
Действительно, проблема исчезает с атрибутом @XmlElement (name = xxxxx). Вы можете изменить этот атрибут во время генерации с помощью jaxb2-annotate-plugin Maven.   -  person serg ogoltsov    schedule 17.09.2016