如何解组由WSImport为子集合生成的嵌套静态包装类

时间:2015-11-02 11:42:29

标签: soap jaxb wsdl unmarshalling wsimport

我正在编写一个基于SOAP的Web服务和一个客户端应用程序,它使用从XML文件读取的导入数据来调用Web服务。 我使用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来解组Books列表。解组XML文件时,我无法获取&lt; authors&gt;的子项。元素,因此由unmarshaller创建的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;

我看了一下wsimport生成的客户端的Book.java - 作者列表已被转换为嵌套的静态类:

/**
 * <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;
    }

}

我注意到@XmlType注释的name属性已经创建为空,如果我用@XmlType(name="")替换@XmlType(name="authors"),则取消编组成功捕获Book的Author子元素:

<?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树来解决问题,使用TreeWalker遍历DOM树。对于每个节点,如果节点是&lt; Book&gt; element我解组了这个“Book node”并将它推送到一个单独的Books列表中。如果遍历的节点是&lt; Author&gt; element,我解组了“作者节点”并将作者对象添加到书籍列表中的最后一个Book对象:

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;
}

这个解决方案有效,但我发现它非常不灵活,我认为必须有一种更简单的方法来成功解组Books的嵌套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是否应该能够使用wsimport生成的类默认捕获XML层次结构的所有子级?

张贴了这么多代码,但也许它可能与解决问题有关。希望任何人都能暗示我走向正确的方向。谢谢!

0 个答案:

没有答案