Define Spring JAXB namespaces without using NamespacePrefixMapper Define Spring JAXB namespaces without using NamespacePrefixMapper spring spring

Define Spring JAXB namespaces without using NamespacePrefixMapper


[Some edits to offer a JAXB-RI alternative are at the end of this post]

Well after much head scratching I've finally had to accept that for my environment (JDK1.6.0_12 on Windows XP and JDK1.6.0_20 on Mac Leopard) I just can't make this work without resorting to the evil that is the NamespacePrefixMapper. Why is it evil? Because it forces a reliance on an internal JVM class in your production code. These classes do not form part of a reliable interface between the JVM and your code (i.e. they change between updates of the JVM).

In my opinion Sun should address this issue or someone with deeper knowledge could add to this answer - please do!

Moving on. Because NamespacePrefixMapper is not supposed to be used outside of the JVM it is not included in the standard compile path of javac (a subsection of rt.jar controlled by ct.sym). This means that any code that depends on it will probably compile fine in an IDE, but will fail at the command line (i.e. Maven or Ant). To overcome this the rt.jar file must be explicitly included in the build, and even then Windows seems to have trouble if the path has spaces in it.

If you find yourself in this position, here is a Maven snippet that will get you out of trouble:

<dependency>  <groupId>com.sun.xml.bind</groupId>  <artifactId>jaxb-impl</artifactId>  <version>2.1.9</version>  <scope>system</scope>  <!-- Windows will not find rt.jar if it is in a path with spaces -->  <systemPath>C:/temp/rt.jar</systemPath></dependency>

Note the rubbish hard coded path to a weird place for rt.jar. You could get around this with a combination of {java.home}/lib/rt.jar which will work on most OSs but because of the Windows space issue is not guaranteed. Yes, you can use profiles and activate accordingly...

Alternatively, in Ant you can do the following:

<path id="jre.classpath">  <pathelement location="${java.home}\lib" /></path>// Add paths for build.classpath and define {src},{target} as usual<target name="compile" depends="copy-resources">  <mkdir dir="${target}/classes"/>  <javac bootclasspathref="jre.classpath" includejavaruntime="yes" debug="on" srcdir="${src}" destdir="${target}/classes" includes="**/*">    <classpath refid="build.classpath"/>  </javac></target>    

And what of the Jaxb2Marshaller Spring configuration? Well here it is, complete with my own NamespacePrefixMapper:

Spring:

<!-- JAXB2 marshalling (domain objects annotated with JAXB2 meta data) --><bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"><property name="contextPaths">  <list>    <value>org.example.domain</value>  </list></property><property name="marshallerProperties">  <map>    <!-- Good for JDK1.6.0_6+, lose 'internal' for earlier releases - see why it's evil? -->    <entry key="com.sun.xml.internal.bind.namespacePrefixMapper" value-ref="myCapabilitiesNamespacePrefixMapper"/>    <entry key="jaxb.formatted.output"><value type="boolean">true</value></entry>  </map></property></bean><!-- Namespace mapping prefix (ns1->abc, ns2->xlink etc) --><bean id="myNamespacePrefixMapper" class="org.example.MyNamespacePrefixMapper"/>

Then my NamespacePrefixMapper code:

public class MyNamespacePrefixMapper extends NamespacePrefixMapper {  public String getPreferredPrefix(String namespaceUri,                               String suggestion,                               boolean requirePrefix) {    if (requirePrefix) {      if ("http://www.example.org/abc".equals(namespaceUri)) {        return "abc";      }      if ("http://www.w3.org/1999/xlink".equals(namespaceUri)) {        return "xlink";      }      return suggestion;    } else {      return "";    }  }}

Well there it is. I hope this helps someone avoid the pain I went through. Oh, by the way, you may run into the following exception if you use the above evil approach within Jetty:

java.lang.IllegalAccessError: class sun.reflect.GeneratedConstructorAccessor23 cannot access its superclass sun.reflect.ConstructorAccessorImpl

So good luck sorting that one out. Clue: rt.jar in the bootclasspath of your web server.

[Extra edits to show the JAXB-RI (Reference Implementation) approach]

If you're able to introduce the JAXB-RI libraries into your code you can make the following modifications to get the same effect:

Main:

// Add a new property that implies external accessmarshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());

MyNamespacePrefixMapper:

// Change the import to thisimport com.sun.xml.bind.marshaller.NamespacePrefixMapper;

Add the following JAR from JAXB-RI download (after jumping through license hoops) from the /lib folder:

jaxb-impl.jar

Running Main.main() results in the desired output.


(Heavily edited reponse)

I believe the problem in your code is due to some namespace URI mismatches. Sometimes you are using "http://www.example.org/abc" and other times "www.example.org/abc". The following should do the trick:

Main.java

package org.example.domain;import javax.xml.bind.JAXBContext;import javax.xml.bind.JAXBException;import javax.xml.bind.Marshaller;public class Main {    public static void main(String[] args) throws JAXBException {         JAXBContext jc = JAXBContext.newInstance(RootElement.class);         System.out.println(jc);        RootElement re = new RootElement();         re.childElementWithXlink = new ChildElementWithXlink();         Marshaller marshaller = jc.createMarshaller();         marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);         marshaller.marshal(re, System.out);       } }

RootElement.java

package org.example.domain; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(namespace="http://www.example.org/abc", name="Root_Element") public class RootElement {   @XmlElement(namespace = "http://www.example.org/abc")   public ChildElementWithXlink childElementWithXlink; }

ChildElementWithXLink.java

package org.example.domain;import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSchemaType; @XmlRootElement(namespace="http://www.example.org/abc", name="Child_Element_With_XLink") public class ChildElementWithXlink {   @XmlAttribute(namespace = "http://www.w3.org/1999/xlink")   @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI")   private String href="http://www.example.org"; } 

package-info.java

@javax.xml.bind.annotation.XmlSchema(     namespace = "http://www.example.org/abc",     xmlns = {           @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"),           @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink")             },      elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)     package org.example.domain; 

Now running Main.main() gives the following output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><abc:Root_Element xmlns:abc="http://www.example.org/abc" xmlns:xlink="http://www.w3.org/1999/xlink">    <abc:childElementWithXlink xlink:href="http://www.example.org"/></abc:Root_Element>


@Blaise: Can you update the documentation of MOXy with this info:

Define Spring JAXB namespaces without using NamespacePrefixMapper

I think it is not described there how you can configure the namespace prefixes.Thanks!