Search code examples
springspring-mvcspring-bootjaxb2

Returning JAXB-generated elements from Spring Boot Controller


I'm generating a plethora of Java files from http://www.ncpdp.org's XSD files (only available to members). After generating them, I'd like to use them in my Spring Controllers, but I'm having problems getting responses converted to XML.

I've tried returning the element itself, as well as JAXBElement<T>, but neither seems to work. The test below fails:

java.lang.AssertionError: Status 
Expected :200
Actual   :406

@Test
public void testHelloWorld() throws Exception {
    mockMvc.perform(get("/api/message")
            .accept(MediaType.APPLICATION_XML)
            .contentType(MediaType.APPLICATION_XML))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML));
}

Here's my Controller:

import org.ncpdp.schema.transport.MessageType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {

    @RequestMapping(value = "/api/message", method = RequestMethod.GET)
    public MessageType messageType() {
        return new MessageType();
    }
}

I've tried creating a MvcConfig to override Spring Boot's MVC config, but it doesn't seem to be working.

@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(marshallingHttpMessageConverter());
    }

    @Bean
    public MarshallingHttpMessageConverter marshallingHttpMessageConverter() {
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setPackagesToScan(new String[]{"com.ncpdb.schema.transport"});

        MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
        converter.setMarshaller(jaxb2Marshaller);
        converter.setUnmarshaller(jaxb2Marshaller);
        converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML));

        return converter;
    }
}

What do I need to do to get Spring MVC to marshall my generated JAXB objects as XML?


Solution

  • I was able to solve this by creating a bindings.xjb file in the same directory as my schemas. This causes JAXB to generate @XmlRootElement on classes.

    <?xml version="1.0"?>
    <jxb:bindings version="1.0"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
                  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd">
    
        <jxb:bindings schemaLocation="transport.xsd" node="/xsd:schema">
            <jxb:globalBindings>
                <xjc:simple/>
            </jxb:globalBindings>
        </jxb:bindings>
    </jxb:bindings>
    

    To add namespaces prefixes to the returned XML, I had to modify the maven-jaxb2-plugin to add a couple arguments.

    <arg>-extension</arg>
    <arg>-Xnamespace-prefix</arg>
    

    And add a dependency:

    <dependencies>
        <dependency>
            <groupId>org.jvnet.jaxb2_commons</groupId>
            <artifactId>jaxb2-namespace-prefix</artifactId>
            <version>1.1</version>
        </dependency>
    </dependencies>
    

    Then modify my bindings.xjb to include this:

    <?xml version="1.0"?>
    <jxb:bindings version="1.0"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
                  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:namespace="http://jaxb2-commons.dev.java.net/namespace-prefix"
                  xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd
                  http://jaxb2-commons.dev.java.net/namespace-prefix http://java.net/projects/jaxb2-commons/sources/svn/content/namespace-prefix/trunk/src/main/resources/prefix-namespace-schema.xsd">
    
        <jxb:bindings schemaLocation="transport.xsd" node="/xsd:schema">
            <jxb:globalBindings>
                <xjc:simple/>
            </jxb:globalBindings>
    
            <jxb:schemaBindings>
                <jxb:package name="org.ncpdp.schema.transport"/>
            </jxb:schemaBindings>
            <jxb:bindings>
                <namespace:prefix name="transport"/>
            </jxb:bindings>
        </jxb:bindings>
    </jxb:bindings>
    

    I learned how to do this from https://java.net/projects/jaxb2-commons/pages/Namespace-prefix. I also found http://blog.frankel.ch/customize-your-jaxb-bindings to be a good resource on how to customize JAXB bindings.