Reputation: 43
I met a problem that looks simple but not obvious to solve.
I have 2 packages with classes marshalled to xml with different namespaces. One class is container for others.
package namespace1;
@XmlRootElement(name = "container", namespace = "urn:nmsp:container:001.02")
@XmlAccessorType(XmlAccessType.FIELD)
@Getter
@Setter
public class Container {
@XmlElement(name = "name")
private String name;
@XmlElement(name = "messages")
private List<Message> messages;
}
-------------------------------------------
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Message", propOrder = {
"document"
})
public class Message {
@XmlElement(name = "Document", namespace = "urn:iso:std:iso:20022:001", required = true)
protected Document document;
}
-------------------------------------------
package-info.java
@javax.xml.bind.annotation.XmlSchema(namespace = "urn:nmsp:container:001.02",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED,
xmlns = {
@XmlNs(namespaceURI = "urn:nmsp:container:001.02", prefix = "")
})
package namespace1;
and
package namespace2;
@XmlRootElement(name = "document", namespace = "urn:iso:std:iso:20022:001")
@XmlAccessorType(XmlAccessType.FIELD)
public class Document {
@XmlElement(name = "id")
private String id;
}
package-info.java
@javax.xml.bind.annotation.XmlSchema(namespace = "urn:iso:std:iso:20022:001",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED,
xmlns = {
@XmlNs(namespaceURI = "urn:iso:std:iso:20022:001", prefix = "")
})
package namespace2;
What I would like to get is:
<container xmlns="urn:nmsp:container:001.02">
<name>Hello</name>
<messages>
<Document xmlns="urn:iso:std:iso:20022:001">
<id>ID1</id>
</Document>
</messages>
</container>
But what I get is:
<container xmlns="urn:nmsp:container:001.02" xmlns:ns2="urn:iso:std:iso:20022:001">
<name>Hello</name>
<messages>
<ns2:Document>
<ns2:id>ID1</ns2:id>
</ns2:Document>
</messages>
</container>
Marshalling code is:
Container conxml = new Container("Hello");
conxml.setMessages(List.of(new Message(new Document("ID1")))));
JAXBContext jc = JAXBContext.newInstance(Container.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
StringWriter writer = new StringWriter();
QName _Conxml_QNAME = new QName("urn:nmsp:container:001.02", "container");
JAXBElement<Container> conxmlJAXBElement = new JAXBElement<>(_Conxml_QNAME, Container.class, null, conxml);
marshaller.marshal(conxmlJAXBElement, writer);
System.out.println(writer.toString());
Almost all solutions suggest to fix it with package-info.java but it doesn't help practically. Can you please give me a hint where I am doing it wrong?
The link to maven project: JaxbTest.7z
Upvotes: 2
Views: 970
Reputation: 16045
The two XML files you give are equivalent, your choice of namespace declaration is a question of taste. The prefixes you set in the @XmlNs
annotations are taken into account, when choosing the prefix for each namespace. However, JAXB RI at least, does not redefine them mid-document.
If you want to more directly intervene in the way the XML is generated, you need to add additional components between JAXB and the Writer
. E.g. you can use StAX and marshall your document to an XMLStreamWriter like this one (the IndentingXMLStreamWriter
comes from TXW2, a dependency of JAXB RI):
private static class RedefineNsXmlStreamWriter extends IndentingXMLStreamWriter {
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
final String known_prefix = getNamespaceContext().getPrefix(namespaceURI);
super.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, localName, namespaceURI);
if (!XMLConstants.DEFAULT_NS_PREFIX.equals(known_prefix)) {
super.writeNamespace(XMLConstants.DEFAULT_NS_PREFIX, namespaceURI);
}
}
@Override
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
// Ignore it
}
public RedefineNsXmlStreamWriter(XMLStreamWriter out) {
super(out);
}
}
Then you create an XMLStreamWriter
capable of writing to the Writer
and serialize:
final XMLStreamWriter xmlWriter = new RedefineNsXmlStreamWriter(XMLOutputFactory.newFactory()//
.createXMLStreamWriter(writerCont));
marshallerCont.marshal(conxmlJAXBElement, xmlWriter);
Of course the RedefineNsXmlStreamWriter
get more complicated once you take into account all cases, but it works for your example.
Upvotes: 2