I have a web service I have to call using a proxy generated using xcj
(for .xsd
) and wsimport
(for .wsdl
) definitions. It is external (i.e. cannot edit it), huge and ever-changing (cannot hand-tweak generated classes).
It has a superclass-like xs:anyType
element with mixed="true"
, similar to sample below.
<Foo type="Bar">
<id>123</id>
</Foo>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Foo">
<xs:complexType>
<xs:complexContent mixed="true">
<xs:extension base="xs:anyType">
<xs:attribute name="type" type="xs:string"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="Bar">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:int"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
xjc
/wsimport
generates Foo
as follows
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "content" })
@XmlRootElement(name = "Foo")
public class Foo {
@XmlMixed
@XmlAnyElement
protected List<Object> content;
@XmlAttribute(name = "type")
protected String type;
@XmlAnyAttribute
private Map<QName, String> otherAttributes = new HashMap<QName, String>();
I can marshall a Foo object to its root-less form using Document.getChildNodes()
(although I wouldn't mind finding a cleaner way), but I cannot figure out how to unmarshal response's content
list (which is ArrayList<ElementNSImpl>
after proxy is done with it) to strongly-typed Bar
?
public static void main(String[] args) throws Exception {
Foo foo = new Foo();
foo.type = "Bar";
Bar bar = new Bar();
bar.id = 123;
JAXBContext context = JAXBContext.newInstance(Foo.class, Bar.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
marshaller.marshal(bar, document);
NodeList nodes = document.getFirstChild().getChildNodes();
foo.content = IntStream.range(0, nodes.getLength()).mapToObj(nodes::item).collect(Collectors.toList());
StringWriter writer = new StringWriter();
marshaller.marshal(foo, writer);
System.out.println(writer);
Unmarshaller unmarshaller = context.createUnmarshaller();
// ???
unmarshaller.unmarshal(foo.content, Bar.class);
}
I think I solved it. Upvotes to whomever comes up a cleaner solution though.
public static List<Object> marshal(Object value) {
try {
Class<?> type = value.getClass();
if (type.isAnonymousClass())
type = type.getSuperclass();
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Marshaller marshaller = JAXBContext.newInstance(type).createMarshaller();
marshaller.marshal(new JAXBElement<>(QName.valueOf("root"), Object.class, value), document);
NodeList nodes = document.getDocumentElement().getChildNodes();
return IntStream.range(0, nodes.getLength()).mapToObj(nodes::item).collect(Collectors.toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static <T> T unmarshal(List<Object> content, Map<QName, String> attributes, Class<T> type) {
try {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
document.appendChild(document.createElement("root"));
if (attributes != null)
attributes.forEach((q, s) -> document.getDocumentElement().setAttribute(q.getLocalPart(), s));
if (content != null)
content.forEach(o -> document.getDocumentElement().appendChild(document.importNode((Node) o, true)));
Unmarshaller unmarshaller = JAXBContext.newInstance(type).createUnmarshaller();
return unmarshaller.unmarshal(document, type).getValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
}