Spring Framework
  1. Spring Framework
  2. SPR-6577

MarshallingView auto detect model with Jaxb2Marshaller chooses the wrong object

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 3.0 GA
    • Fix Version/s: 3.0.1
    • Component/s: OXM, Web
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      When using MarshallingView with a Jaxb2Marshaller and more than one object in the model has JAXB annotations on it, automatic detection of the appropriate object in the model does not work as expected.

      Using this example MVC RESTful service:

      Request and Response classes:

      @XmlRootElement(name = "request")
      public class HelloRequest {
        private String name;
        private String title;
        // getters and setters
      }
      
      @XmlRootElement(name = "response")
      public class HelloResponse {
        private String message;
        // getters and setters
      }
      

      Controller:

      @Controller
      @RequestMapping("/Hello")
      public class HelloController {
        @RequestMapping(value = "/greet", method = RequestMethod.GET)
        public HelloResponse greetGet(HelloRequest request) {
          String message = "Hello, " + request.getTitle() + " " + request.getName();
          return new HelloResponse(message);
        }
      }
      

      -servlet.xml

      <beans xmlns=...>
        <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
        <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
      
        <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
          <property name="mediaTypes">
            <map><entry key="xml" value="application/xml"/></map>
          </property>
          <property name="viewResolvers">
            <list>
              <bean class="org.springframework.web.servlet.view.ContentTypeViewResolver" p:order="1" p:view-ref="xmlView"/>
            </list>
          </property>
          <property name="order" value="0"/>
        </bean>
      
        <bean id="xmlView" class="org.springframework.web.servlet.view.xml.MarshallingView">
          <property name="marshaller" ref="marshaller"/>
        </bean>
      
        <oxm:jaxb2-marshaller id="marshaller">
          <oxm:class-to-be-bound name="com.test.domain.HelloResponse"/>
        </oxm:jaxb2-marshaller>
      
        <bean name="helloMvc" class="com.test.hello.HelloController"/>
      </beans>
      

      In this scenario, the Controller simply returns a response object. The model is automatically generated, and includes both the request and response objects (along with a few other objects). The model is passed to the MarshallingView, which tries to detect the appropriate model object for rendering the view (since no modelKey property was set on the MarshallingView). MarshallingView calls the supports() method of the configured Marshaller with each object in the model until supports() returns true. The detected object is then passed to the Marshaller for marshalling.

      When MarshallingView is used with Jaxb2Marshaller this causes a problem. Jaxb2Marshaller.supports(Class<?> c) will return true for any type that has a JAXB @XmlRootElement annotation on it, even if that Marshaller is not configured to support that class. In this scenario, MarshallingView encounters the request object in the model first and passes it to Jaxb2Marshaller.supports(), which returns true because the request object is annotated with @XmlRootElement. MarshallingView then passes the request object to Jaxb2Marshaller.marshal(). This call fails because the Marshaller is not configured to handle HelloRequest objects:

      org.springframework.oxm.UncategorizedMappingException: Unknown JAXB exception; nested exception is
       javax.xml.bind.JAXBException: class com.test.domain.HelloRequest nor any of its super class is known to this context.
      	at org.springframework.oxm.jaxb.Jaxb2Marshaller.convertJaxbException(Jaxb2Marshaller.java:580)
      	at org.springframework.oxm.jaxb.Jaxb2Marshaller.marshal(Jaxb2Marshaller.java:412)
      	at org.springframework.oxm.jaxb.Jaxb2Marshaller.marshal(Jaxb2Marshaller.java:395)
      	at org.springframework.web.servlet.view.xml.MarshallingView.renderMergedOutputModel(MarshallingView.java:103)
      	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:250)
      	at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1060)
      	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:798)
      	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)
      	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)
      	... 37 more
      Caused by: javax.xml.bind.JAXBException: class com.test.domain.HelloRequest nor any of its super class is known to this context.
      	at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:554)
      	at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:470)
      	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
      	at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
      	at org.springframework.oxm.jaxb.Jaxb2Marshaller.marshal(Jaxb2Marshaller.java:408)
      	... 44 more
      

      If the Jaxb2Marshaller were configured to support HelloRequest objects, then this exception would not be thrown but the request object would incorrectly be marshalled to the output view instead of the response object.

      To fix this, either the @XmlRootElement must be removed from the HelloRequest class, or the modelKey property must be set on the MarshallingView (to "helloResponse" in this case) to override automatic detection of the appropriate model object. Neither of these is a good solution.

      • The HelloRequest class does not need the @XmlRootElement annotation on it for this example, but in the real-world scenario this class is also used for other POST requests with the @RequestBody annotation.
      • Setting the modelKey property on the view would require a unique view bean to match each response object, since the name of the response object class is used to generate the model key.

        Activity

        Hide
        Scott Frederick added a comment -

        It seems that the Jaxb2Marshaller.supports(Class clazz) method is too greedy for this scenario. I tried extending Jaxb2Marshaller and overriding the supports() method to eliminate the "JAXBElement.class.isAssignableFrom(clazz)" and "AnnotationUtils.findAnnotation(clazz, XmlRootElement.class)" checks, but that was not possible because the contextPath and classesToBeBound fields of the class are private.

        Would it make sense to add a property to Jaxb2Marshaller so it could be configured to only support classes that are configured through the contextPath or classesToBeBound property?

        Otherwise, it seems like there would need to be changes to how the default model is created or how MarshallingView.locateToBeMarshalled() does its auto-detection.

        Show
        Scott Frederick added a comment - It seems that the Jaxb2Marshaller.supports(Class clazz) method is too greedy for this scenario. I tried extending Jaxb2Marshaller and overriding the supports() method to eliminate the "JAXBElement.class.isAssignableFrom(clazz)" and "AnnotationUtils.findAnnotation(clazz, XmlRootElement.class)" checks, but that was not possible because the contextPath and classesToBeBound fields of the class are private. Would it make sense to add a property to Jaxb2Marshaller so it could be configured to only support classes that are configured through the contextPath or classesToBeBound property? Otherwise, it seems like there would need to be changes to how the default model is created or how MarshallingView.locateToBeMarshalled() does its auto-detection.
        Hide
        Arjen Poutsma added a comment -

        I've revised the supports() method to be more restrictive. It now only supports classes either in the context path or explicitly supported.

        I've also added getters for context path and classesToBeBound, so that you can easily override these methods.

        Show
        Arjen Poutsma added a comment - I've revised the supports() method to be more restrictive. It now only supports classes either in the context path or explicitly supported. I've also added getters for context path and classesToBeBound, so that you can easily override these methods.

          People

          • Assignee:
            Arjen Poutsma
            Reporter:
            Scott Frederick
            Last updater:
            Trevor Marshall
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Days since last comment:
              4 years, 12 weeks, 2 days ago

              Time Tracking

              Estimated:
              Original Estimate - Not Specified
              Not Specified
              Remaining:
              Remaining Estimate - 0d
              0d
              Logged:
              Time Spent - 1h 55m
              1h 55m