From e4aabd5288c981a0ec0d9a21d94e4eca58206c9b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 30 May 2014 17:27:34 +0200 Subject: [PATCH] MarshallingView unwraps JAXBElement value for Marshaller.supports(Class) check Issue: SPR-11827 --- .../web/servlet/view/xml/MarshallingView.java | 42 ++++++++++++++----- .../view/xml/MarshallingViewTests.java | 22 ++++++++++ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java index 16c815f314..582807358b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MarshallingView.java @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.bind.JAXBElement; import javax.xml.transform.stream.StreamResult; import org.springframework.oxm.Marshaller; @@ -55,7 +56,7 @@ public class MarshallingView extends AbstractView { /** - * Constructs a new {@code MarshallingView} with no {@link Marshaller} set. + * Construct a new {@code MarshallingView} with no {@link Marshaller} set. * The marshaller must be set after construction by invoking {@link #setMarshaller}. */ public MarshallingView() { @@ -74,7 +75,7 @@ public class MarshallingView extends AbstractView { /** - * Sets the {@link Marshaller} to be used by this view. + * Set the {@link Marshaller} to be used by this view. */ public void setMarshaller(Marshaller marshaller) { this.marshaller = marshaller; @@ -124,23 +125,42 @@ public class MarshallingView extends AbstractView { */ protected Object locateToBeMarshalled(Map model) throws IllegalStateException { if (this.modelKey != null) { - Object obj = model.get(this.modelKey); - if (obj == null) { + Object value = model.get(this.modelKey); + if (value == null) { throw new IllegalStateException("Model contains no object with key [" + this.modelKey + "]"); } - if (!this.marshaller.supports(obj.getClass())) { - throw new IllegalStateException("Model object [" + obj + "] retrieved via key [" + + if (!isEligibleForMarshalling(this.modelKey, value)) { + throw new IllegalStateException("Model object [" + value + "] retrieved via key [" + this.modelKey + "] is not supported by the Marshaller"); } - return obj; + return value; } - for (Object obj : model.values()) { - if (obj != null && (model.size() == 1 || !(obj instanceof BindingResult)) && - this.marshaller.supports(obj.getClass())) { - return obj; + for (Map.Entry entry : model.entrySet()) { + Object value = entry.getValue(); + if (value != null && (model.size() == 1 || !(value instanceof BindingResult)) && + isEligibleForMarshalling(entry.getKey(), value)) { + return value; } } return null; } + /** + * Check whether the given value from the current view's model is eligible + * for marshalling through the configured {@link Marshaller}. + *

The default implementation calls {@link Marshaller#supports(Class)}, + * unwrapping a given {@link JAXBElement} first if applicable. + * @param modelKey the value's key in the model (never {@code null}) + * @param value the value to check (never {@code null}) + * @return whether the given value is to be considered as eligible + * @see Marshaller#supports(Class) + */ + protected boolean isEligibleForMarshalling(String modelKey, Object value) { + Class classToCheck = value.getClass(); + if (value instanceof JAXBElement) { + classToCheck = ((JAXBElement) value).getDeclaredType(); + } + return this.marshaller.supports(classToCheck); + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/xml/MarshallingViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/xml/MarshallingViewTests.java index 39e58a96ef..b1a3475f1c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/xml/MarshallingViewTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/xml/MarshallingViewTests.java @@ -19,6 +19,8 @@ package org.springframework.web.servlet.view.xml; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; import javax.xml.transform.stream.StreamResult; import org.junit.Before; @@ -35,6 +37,7 @@ import static org.mockito.BDDMockito.*; /** * @author Arjen Poutsma + * @author Juergen Hoeller */ public class MarshallingViewTests { @@ -84,6 +87,25 @@ public class MarshallingViewTests { assertEquals("Invalid content length", 0, response.getContentLength()); } + @Test + public void renderModelKeyWithJaxbElement() throws Exception { + String toBeMarshalled = "value"; + String modelKey = "key"; + view.setModelKey(modelKey); + Map model = new HashMap(); + model.put(modelKey, new JAXBElement(new QName("model"), String.class, toBeMarshalled)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(marshallerMock.supports(String.class)).willReturn(true); + marshallerMock.marshal(eq(toBeMarshalled), isA(StreamResult.class)); + + view.render(model, request, response); + assertEquals("Invalid content type", "application/xml", response.getContentType()); + assertEquals("Invalid content length", 0, response.getContentLength()); + } + @Test public void renderInvalidModelKey() throws Exception { Object toBeMarshalled = new Object();