rygel
rygel

Reputation: 43

Jaxb and namespace of inner element

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

Answers (1)

Piotr P. Karwasz
Piotr P. Karwasz

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

Related Questions