Reputation: 36096
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"/>
</relation>
<relation type="CHILD">
<to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</relation>
<!-- SNIP: some more relations -->
</person>
Wanted:
<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<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>
Upvotes: 20
Views: 40204
Reputation: 1323203
2013: 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 aNamespacePrefixMapper
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 thePreferredMapper
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>
Upvotes: 9
Reputation: 65
Add your nsPrefix
mapping by doing this:
marshaller.setNamespaceMapping("myns","urn:foo");
Upvotes: -1
Reputation: 167
You can do it with the code:
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
@Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] {
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
};
}
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI))
return "xsi";
if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI))
return "xs";
if (namespaceUri.equals(WellKnownNamespace.XML_MIME_URI))
return "xmime";
return suggestion;
}
});
Upvotes: 6
Reputation: 21545
You can let the namespaces be written only once. You will need a proxy class of the XMLStreamWriter and a package-info.java. Then you will do in your code:
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = new Wrapper((XMLStreamWriter) XMLOutputFactory
.newInstance().createXMLStreamWriter(stringWriter));
JAXBContext jaxbContext = JAXBContext.newInstance(Collection.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(books, writer);
System.out.println(stringWriter.toString());
Proxy class (the important method is "writeNamespace"):
class WrapperXMLStreamWriter implements XMLStreamWriter {
private final XMLStreamWriter writer;
public WrapperXMLStreamWriter(XMLStreamWriter writer) {
this.writer = writer;
}
//keeps track of what namespaces were used so that not to
//write them more than once
private List<String> namespaces = new ArrayList<String>();
public void init(){
namespaces.clear();
}
public void writeStartElement(String localName) throws XMLStreamException {
init();
writer.writeStartElement(localName);
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
init();
writer.writeStartElement(namespaceURI, localName);
}
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
init();
writer.writeStartElement(prefix, localName, namespaceURI);
}
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if(namespaces.contains(namespaceURI)){
return;
}
namespaces.add(namespaceURI);
writer.writeNamespace(prefix, namespaceURI);
}
// .. other delegation method, always the same pattern: writer.method() ...
}
package-info.java:
@XmlSchema(elementFormDefault=XmlNsForm.QUALIFIED, attributeFormDefault=XmlNsForm.UNQUALIFIED ,
xmlns = {
@XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package your.package;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Upvotes: 2
Reputation: 1043
This is the best answer I find it in the web.
The xsi:type
declarations are most likely being created because the declared type of the JAXBElement
does not match the type of the value.
If the ObjectFactory
has a create method for the correct JAXBElement
you should use that since it should correctly populate both the QName
and the type info; otherwise I would try setting the declared type (second constructor arg) of the JAXBElement
to String.class
(assuming this is the type of commentTest
) instead of CommentType.Comment
.
Owner: cbrettin
Upvotes: 2
Reputation: 41
if you're using Maven then just add this to your pom:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
no need for PreferredMapper if you configure your annotations as defined in the example above. Although I have a package-info.jave file confugures as follows:
@javax.xml.bind.annotation.XmlSchema(
namespace = "mylovelynamespace1",
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "myns1", namespaceURI = "mylovelynamespace1"),
@javax.xml.bind.annotation.XmlNs(prefix = "myns2", namespaceURI = "mylovelynamespace2")
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.mylovelycompanyname.package;
Upvotes: 4
Reputation: 3364
Not that pretty but you could add an empty schemaLocation to the root element:
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "");
Upvotes: 18
Reputation: 11216
It's XML, so you could process the output using DOM or XSLT to get rid of multiple namespace references.
Upvotes: 0