tags:

views:

1291

answers:

4

I have a JAXB setup where I use a @XmlJavaTypeAdapter to replace objects of type Person with objects of type PersonRef that only contains the person's UUID. This works perfectly fine. However, the generated XML redeclares the same namespace (xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance") every time it's used. While this is generally okay, it just doesn't feel right.

How can I configure JAXB to declare xmlns:xsi at the very beginning of the document? Can I manually add namespace declarations to the root element?

Here's an example of what I want to achive:

Current:

<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a">
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/&gt; 
    </relation> 
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/&gt; 
    </relation>
    <!-- SNIP: some more relations -->
</person>

Wanted:

<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0"/> 
    </relation> 
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a"/> 
    </relation>
    <!-- SNIP: some more relations -->
</person>
A: 

It's XML, so you could process the output using DOM or XSLT to get rid of multiple namespace references.

java.is.for.desktop
Sorry, but I'd rather shoot myself in the foot :)
sfussenegger
But seriously, throwing XSLT or DOM at this task seems a bit drastic. Eventually, this boils down to simply please my desire for aesthetics :)
sfussenegger
JAXB is not that configurable. I think it's the only way to do this (or to let it).
java.is.for.desktop
+1  A: 

It looks like a JAXB customization Namespace mapper issue

When you marshall an XML document using JAXB 1.0, a Marshaller object, a JAXB object that controls the process of marshalling, provides namespace declarations in the resulting XML document. Sometimes the Marshaller produces a lot of namespace declarations that look redundant, for example:

   <?xml version="1.0"?>
   <root>
      <ns1:element xmlns:ns1="urn:foo"> ... </ns1:element>
      <ns2:element xmlns:ns2="urn:foo"> ... </ns2:element>
      <ns3:element xmlns:ns3="urn:foo"> ... </ns3:element>
   </root>

JAXB 2.0 changes this behavior. If you use JAXB 2.0 (or later) to marshal an XML document, the Marshaller declares all statically known namespace Uniform Resource Identifiers (URIs), that is, those URIs that are used as element or attribute names in JAXB annotations.

JAXB may also declare additional namespaces in the middle of an XML document, for example when a qualified name (QName) that is used as an attribute or element value requires a new namespace URI, or when a Document Object Model (DOM) node in a content tree requires a new namespace URI. This behavior might produce an XML document that has a lot of namespace declarations with automatically-generated namespace prefixes.

The problem is that automatically-generated namespace prefixes such as ns1, ns2, and ns3, are not user friendly -- they typically do not help people understand the marshalled XML.

Fortunately, JAXB 2.0 (or later) provides a service provider interface (SPI) named com.sun.xml.bind.marshaller.NamespacePrefixMapper that you can use to specify more helpful namespace prefixes for marshalling.

When the JAXBSample program marshalls the XML document the first time, it does it without using a NamespacePrefixMapper class. As a result, the Marshaller automatically generates a namespace prefix, in this case, ns2.

   <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <ns2:JustAnElement xmlns:ns2="a">
       <foo>true</foo>
   </ns2:JustAnElement>

Example of a configuration avoiding the namespace repetition:

The second marshalling done by the JAXBSample program uses a NamespacePrefixMapper class as follows:

   NamespacePrefixMapper m = new PreferredMapper();
               marshal(jc, e, m);

   public static class PreferredMapper extends NamespacePrefixMapper {
           @Override
           public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
               return "mappedNamespace" + namespaceUri;
           }
       }

The getPreferredPrefix() method in the PreferredMapper class returns the preferred prefix, in this case, mappedNamespacea to be declared at the root element of the marshalled XML.

   <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <mappedNamespacea:JustAnElement xmlns:mappedNamespacea="a">
       <foo>true</foo>
   </mappedNamespacea:JustAnElement>
VonC
See also http://khylo.blogspot.com/2008/08/jaxb-multiple-namespace-definitions.html on a non-directly related issue.
VonC
Thanks for your answer. The default NamespacePrefixMapper already maps "http://www.w3.org/2001/XMLSchema-instance" to "xsi" (see http://j.mp/dkCcgC). So I don't think that there's a way to declare namespaces earlier using NamespacePrefixMapper. NamespaceContext looks promising though. I'm still trying to figure out how to get my hands on it though. (anyway +1 for your effort)
sfussenegger
In case you're interested: 3 months later, it turns out that your answer wasn't too far away from being correct: right class, wrong method (see Dany's answer). I can't believe I didn't note getPreDeclaredNamespaceUris() method while examining your suggestion.
sfussenegger
@sfussenegger: indeed I am still interested;) And I have upvoted Dany's answer.
VonC
+2  A: 

Not that pretty but you could add an empty schemaLocation to the root element:

marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "");
msparer
indeed not *that* pretty :) but at least it's close to a correct answer.
sfussenegger
+2  A: 

You can do it with the code:

marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
                @Override
                public String[] getPreDeclaredNamespaceUris() {
                    return new String[] { WellKnownNamespace.XML_SCHEMA_INSTANCE };
                }

                @Override
                public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
                    if (namespaceUri.equals(WellKnownNamespace.XML_SCHEMA_INSTANCE))
                        return "xsi";
                    if (namespaceUri.equals(WellKnownNamespace.XML_SCHEMA))
                        return "xs";
                    if (namespaceUri.equals(WellKnownNamespace.XML_MIME_URI))
                        return "xmime";
                    return suggestion;

                }
            });
Dany
That's perfectly correct, thanks. Unfortunately, SO won't let me change the correct answer anymore.
sfussenegger