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