In a previous post I covered how to use namespace qualification with JAXB. In this post I will cover how to control the prefixes that are used. This is not covered in the JAXB (JSR-222) specification but I will demonstrate the extensions available in both the reference and EclipseLink MOXy implementations for handling this use case.
Java Model
package blog.prefix;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(namespace="http://www.example.com/FOO")
public class Root {
private String a;
private String b;
private String c;
@XmlElement(namespace="http://www.example.com/BAR")
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
@XmlElement(namespace="http://www.example.com/FOO")
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
@XmlElement(namespace="http://www.example.com/OTHER")
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
}
Demo Code
We will use the following code to populate the domain model and produce the XML.
package blog.prefix;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext ctx = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.setA("A");
root.setB("B");
root.setC("OTHER");
Marshaller m = ctx.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(root, System.out);
}
}
Output
XML like the following is produced by default. The JAXB implementation has arbitrarily assigned prefixes to the namespace URIs specified in the domain model:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:root
xmlns="http://www.example.com/BAR"
xmlns:ns2="http://www.example.com/FOO"
xmlns:ns3="http://www.example.com/OTHER">
<a>A</a>
<ns2:b>B</ns2:b>
<ns3:c>OTHER</ns3:c>
</ns2:root>
Specify Prefix Mappings with JAXB RI & Metro JAXB
The reference and Metro implementations of JAXB provide a mechanism called NamespacePrefixMapper to control the prefixes that will be assigned to namespaces.
NamespacePrefixMapper
package blog.prefix;
import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
//import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
public class MyNamespaceMapper extends NamespacePrefixMapper {
private static final String FOO_PREFIX = ""; // DEFAULT NAMESPACE
private static final String FOO_URI = "http://www.example.com/FOO";
private static final String BAR_PREFIX = "bar";
private static final String BAR_URI = "http://www.example.com/BAR";
@Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if(FOO_URI.equals(namespaceUri)) {
return FOO_PREFIX;
} else if(BAR_URI.equals(namespaceUri)) {
return BAR_PREFIX;
}
return suggestion;
}
@Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] { FOO_URI, BAR_URI };
}
}
Demo Code
The NamespacePrefixMapper is set on an instance of Marshaller. I would recommend wrapping the setPropery call in a try/catch block so that your application does not fail if you change JAXB implementations.
package blog.prefix;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext ctx = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.setA("A");
root.setB("B");
root.setC("OTHER");
Marshaller m = ctx.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
try {
m.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", new MyNamespaceMapper());
//m.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespaceMapper());
} catch(PropertyException e) {
// In case another JAXB implementation is used
}
m.marshal(root, System.out);
}
}
Output
The resulting document now uses the NamespacePrefixMapper to determine the prefixes that should be used in the resulting document.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root
xmlns:bar="http://www.example.com/BAR"
xmlns="http://www.example.com/FOO"
xmlns:ns3="http://www.example.com/OTHER">
<bar:a>A</bar:a>
<b>B</b>
<ns3:c>OTHER</ns3:c>
</root>
Specify Prefix Mappings with EclipseLink JAXB (MOXy)
MOXy will use the namespace prefixes as they are defined on the @XmlSchema annotation. In order for MOXy to be able to use the default namespace the elementFormDefault property on the @XmlSchema annotation must be set to XmlNsForm.QUALIFIED.
package-info
@XmlSchema(
elementFormDefault=XmlNsForm.QUALIFIED,
namespace="http://www.example.com/FOO",
xmlns={@XmlNs(prefix="bar",
namespaceURI="http://www.example.com/BAR")}
)
package blog.prefix;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Output
The resulting document now uses the xmlns setting from the @XmlSchema annotation to determine the prefixes that should be used in the resulting document.
<?xml version="1.0" encoding="UTF-8"?> <root xmlns="http://www.example.com/FOO" xmlns:ns0="http://www.example.com/OTHER" xmlns:bar="http://www.example.com/BAR"> <bar:a>A</bar:a> <b>B</b> <ns0:c>OTHER</ns0:c> </root>
Further Reading
If you enjoyed this post then you may also be interested in:
Blaise, at least the latest JAXB RI DOES use the @XmlNs annotation for prefix calculation, so the following works with JAXB RI 2.2.4-1 as desired:
ReplyDelete@XmlSchema(
elementFormDefault=XmlNsForm.QUALIFIED,
namespace="http://www.example.com/FOO",
xmlns={
@XmlNs(prefix="", namespaceURI="http://www.example.com/FOO"),
@XmlNs(prefix="bar", namespaceURI="http://www.example.com/BAR")
}
)
However, there's still a difference: RI requires to specify an empty prefix to define the default namespace whereas MOXy takes the default namespace from @XmlSchema.namespace().
When using @XmlNs(prefix="" namespaceURI = "...") as above, MOXy forgets about the default namespace at all and uses "ns0". Hmmm...
Hi Christoph,
ReplyDeleteThank you for reporting that, this appears to be a bug. You can track our progress on this issue using the following link:
- https://bugs.eclipse.org/365457
-Blaise
Thanks, Blaise!
ReplyDelete- Christoph
The correct property is now: com.sun.xml.bind.namespacePrefixMapper -- without the 'internal' package.
ReplyDeleteBlaise,
ReplyDeleteThank you for the msg on twitter. I followed you :). but this one still keeps me busy.
I've implemented a NamespacePrefixMapper and set the property on marshaller object.
try {
jaxbMarshaller.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper",new HRANameSpaceMapper());
} catch(PropertyException e) {
// In case another JAXB implementation is used
}
It now generates ns2, ns3, ns4 and not the ones used in My prefixMapper. below is the getPreferredPrefix()
@Override
public String getPreferredPrefix(String uri, String suggestion, boolean requirePrefix) {
System.out.println("In mapper");
if (requirePrefix) {
if ("hra.xsd".equalsIgnoreCase(uri)) {
return "hra";
}
else if ("mbr.xsd".equalsIgnoreCase(uri)) {
return "mbr";
} else if ("sis.xsd".equalsIgnoreCase(uri)) {
return "sis";
}
}
return suggestion;
}
Any suggestions are appreciated.
Blaise,
ReplyDeleteFinally good news came from Package-info.java.
@XmlSchema(
namespace="hra",
elementFormDefault = XmlNsForm.QUALIFIED,
xmlns={
@XmlNs(namespaceURI = "hra", prefix = "hra"),
@XmlNs(namespaceURI = "sis_message", prefix = "sis"),
@XmlNs(namespaceURI = "member", prefix = "mbr"),
})
And we do not need the NamespacePrefixmanager setting to the Marshaller.
Thanks,
Mahesh
Hi Mahesh,
DeleteThat is great to hear. One thing I notice is that in your implementation of NamespacePrefixMapper you have the namespace URI suffixed with ".xsd". If you removed that to match what you did with the @XmlSchema annotation that should work as well.
-Blaise