Я пишу веб-службу на основе 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>
* <complexType>
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element ref="{http://technikumwien.at/}author" maxOccurs="unbounded" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </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 по умолчанию?
Извините за публикацию такого большого количества кода, но, возможно, это может иметь значение для решения проблемы. Надеюсь, кто-нибудь может намекнуть мне в правильном направлении. Спасибо!
<authors>
)? Без него все будет хорошо. - person laune   schedule 02.11.2015