Skip to content

Commit 648245b

Browse files
committed
MarshallingView should not close response OutputStream after copying to it
Also throws IllegalStateException instead of ServletException now, consistent with other Spring MVC classes. Issue: SPR-11411
1 parent 7301b58 commit 648245b

File tree

2 files changed

+54
-56
lines changed

2 files changed

+54
-56
lines changed
Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,26 +18,24 @@
1818

1919
import java.io.ByteArrayOutputStream;
2020
import java.util.Map;
21-
22-
import javax.servlet.ServletException;
2321
import javax.servlet.http.HttpServletRequest;
2422
import javax.servlet.http.HttpServletResponse;
2523
import javax.xml.transform.stream.StreamResult;
2624

27-
import org.springframework.beans.BeansException;
2825
import org.springframework.oxm.Marshaller;
2926
import org.springframework.util.Assert;
30-
import org.springframework.util.FileCopyUtils;
27+
import org.springframework.util.StreamUtils;
3128
import org.springframework.web.servlet.View;
3229
import org.springframework.web.servlet.view.AbstractView;
3330

3431
/**
35-
* Spring-MVC {@link View} that allows for response context to be rendered as the result of marshalling by a {@link
36-
* Marshaller}.
32+
* Spring-MVC {@link View} that allows for response context to be rendered as the result
33+
* of marshalling by a {@link Marshaller}.
3734
*
38-
* <p>The Object to be marshalled is supplied as a parameter in the model and then {@linkplain
39-
* #locateToBeMarshalled(Map) detected} during response rendering. Users can either specify a specific entry in the
40-
* model via the {@link #setModelKey(String) sourceKey} property or have Spring locate the Source object.
35+
* <p>The Object to be marshalled is supplied as a parameter in the model and then
36+
* {@linkplain #locateToBeMarshalled(Map) detected} during response rendering. Users can
37+
* either specify a specific entry in the model via the {@link #setModelKey(String) sourceKey}
38+
* property or have Spring locate the Source object.
4139
*
4240
* @author Arjen Poutsma
4341
* @since 3.0
@@ -49,13 +47,15 @@ public class MarshallingView extends AbstractView {
4947
*/
5048
public static final String DEFAULT_CONTENT_TYPE = "application/xml";
5149

50+
5251
private Marshaller marshaller;
5352

5453
private String modelKey;
5554

55+
5656
/**
57-
* Constructs a new {@code MarshallingView} with no {@link Marshaller} set. The marshaller must be set after
58-
* construction by invoking {@link #setMarshaller(Marshaller)}.
57+
* Constructs a new {@code MarshallingView} with no {@link Marshaller} set.
58+
* The marshaller must be set after construction by invoking {@link #setMarshaller}.
5959
*/
6060
public MarshallingView() {
6161
setContentType(DEFAULT_CONTENT_TYPE);
@@ -66,80 +66,79 @@ public MarshallingView() {
6666
* Constructs a new {@code MarshallingView} with the given {@link Marshaller} set.
6767
*/
6868
public MarshallingView(Marshaller marshaller) {
69-
Assert.notNull(marshaller, "'marshaller' must not be null");
70-
setContentType(DEFAULT_CONTENT_TYPE);
71-
this.marshaller = marshaller;
72-
setExposePathVariables(false);
69+
this();
70+
setMarshaller(marshaller);
7371
}
7472

73+
7574
/**
7675
* Sets the {@link Marshaller} to be used by this view.
7776
*/
7877
public void setMarshaller(Marshaller marshaller) {
79-
Assert.notNull(marshaller, "'marshaller' must not be null");
78+
Assert.notNull(marshaller, "Marshaller must not be null");
8079
this.marshaller = marshaller;
8180
}
8281

8382
/**
84-
* Set the name of the model key that represents the object to be marshalled. If not specified, the model map will be
85-
* searched for a supported value type.
86-
*
83+
* Set the name of the model key that represents the object to be marshalled.
84+
* If not specified, the model map will be searched for a supported value type.
8785
* @see Marshaller#supports(Class)
8886
*/
8987
public void setModelKey(String modelKey) {
9088
this.modelKey = modelKey;
9189
}
9290

9391
@Override
94-
protected void initApplicationContext() throws BeansException {
95-
Assert.notNull(marshaller, "Property 'marshaller' is required");
92+
protected void initApplicationContext() {
93+
Assert.notNull(this.marshaller, "Property 'marshaller' is required");
9694
}
9795

96+
9897
@Override
99-
protected void renderMergedOutputModel(Map<String, Object> model,
100-
HttpServletRequest request,
101-
HttpServletResponse response) throws Exception {
98+
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
99+
HttpServletResponse response) throws Exception {
100+
102101
Object toBeMarshalled = locateToBeMarshalled(model);
103102
if (toBeMarshalled == null) {
104-
throw new ServletException("Unable to locate object to be marshalled in model: " + model);
103+
throw new IllegalStateException("Unable to locate object to be marshalled in model: " + model);
105104
}
106105
ByteArrayOutputStream bos = new ByteArrayOutputStream(2048);
107-
marshaller.marshal(toBeMarshalled, new StreamResult(bos));
106+
this.marshaller.marshal(toBeMarshalled, new StreamResult(bos));
108107

109108
setResponseContentType(request, response);
110109
response.setContentLength(bos.size());
111110

112-
FileCopyUtils.copy(bos.toByteArray(), response.getOutputStream());
111+
StreamUtils.copy(bos.toByteArray(), response.getOutputStream());
113112
}
114113

115114
/**
116-
* Locates the object to be marshalled. The default implementation first attempts to look under the configured
117-
* {@linkplain #setModelKey(String) model key}, if any, before attempting to locate an object of {@linkplain
118-
* Marshaller#supports(Class) supported type}.
119-
*
115+
* Locates the object to be marshalled. The default implementation first attempts to look
116+
* under the configured {@linkplain #setModelKey(String) model key}, if any, before attempting
117+
* to locate an object of {@linkplain Marshaller#supports(Class) supported type}.
120118
* @param model the model Map
121119
* @return the Object to be marshalled (or {@code null} if none found)
122-
* @throws ServletException if the model object specified by the {@linkplain #setModelKey(String) model key} is not
123-
* supported by the marshaller
120+
* @throws IllegalStateException if the model object specified by the
121+
* {@linkplain #setModelKey(String) model key} is not supported by the marshaller
124122
* @see #setModelKey(String)
125123
*/
126-
protected Object locateToBeMarshalled(Map<String, Object> model) throws ServletException {
124+
protected Object locateToBeMarshalled(Map<String, Object> model) throws IllegalStateException {
127125
if (this.modelKey != null) {
128-
Object o = model.get(this.modelKey);
129-
if (o == null) {
130-
throw new ServletException("Model contains no object with key [" + modelKey + "]");
126+
Object obj = model.get(this.modelKey);
127+
if (obj == null) {
128+
throw new IllegalStateException("Model contains no object with key [" + this.modelKey + "]");
131129
}
132-
if (!this.marshaller.supports(o.getClass())) {
133-
throw new ServletException("Model object [" + o + "] retrieved via key [" + modelKey +
134-
"] is not supported by the Marshaller");
130+
if (!this.marshaller.supports(obj.getClass())) {
131+
throw new IllegalStateException("Model object [" + obj + "] retrieved via key [" +
132+
this.modelKey + "] is not supported by the Marshaller");
135133
}
136-
return o;
134+
return obj;
137135
}
138-
for (Object o : model.values()) {
139-
if (o != null && this.marshaller.supports(o.getClass())) {
140-
return o;
136+
for (Object obj : model.values()) {
137+
if (obj != null && this.marshaller.supports(obj.getClass())) {
138+
return obj;
141139
}
142140
}
143141
return null;
144142
}
143+
145144
}

spring-webmvc/src/test/java/org/springframework/web/servlet/view/xml/MarshallingViewTests.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,12 +18,11 @@
1818

1919
import java.util.HashMap;
2020
import java.util.Map;
21-
22-
import javax.servlet.ServletException;
2321
import javax.xml.transform.stream.StreamResult;
2422

2523
import org.junit.Before;
2624
import org.junit.Test;
25+
2726
import org.springframework.mock.web.test.MockHttpServletRequest;
2827
import org.springframework.mock.web.test.MockHttpServletResponse;
2928
import org.springframework.oxm.Marshaller;
@@ -93,9 +92,9 @@ public void renderInvalidModelKey() throws Exception {
9392

9493
try {
9594
view.render(model, request, response);
96-
fail("ServletException expected");
95+
fail("IllegalStateException expected");
9796
}
98-
catch (ServletException ex) {
97+
catch (IllegalStateException ex) {
9998
// expected
10099
}
101100
assertEquals("Invalid content length", 0, response.getContentLength());
@@ -112,9 +111,9 @@ public void renderNullModelValue() throws Exception {
112111

113112
try {
114113
view.render(model, request, response);
115-
fail("ServletException expected");
114+
fail("IllegalStateException expected");
116115
}
117-
catch (ServletException ex) {
116+
catch (IllegalStateException ex) {
118117
// expected
119118
}
120119
assertEquals("Invalid content length", 0, response.getContentLength());
@@ -135,9 +134,9 @@ public void renderModelKeyUnsupported() throws Exception {
135134

136135
try {
137136
view.render(model, request, response);
138-
fail("ServletException expected");
137+
fail("IllegalStateException expected");
139138
}
140-
catch (ServletException ex) {
139+
catch (IllegalStateException ex) {
141140
// expected
142141
}
143142
}
@@ -174,9 +173,9 @@ public void testRenderUnsupportedModel() throws Exception {
174173

175174
try {
176175
view.render(model, request, response);
177-
fail("ServletException expected");
176+
fail("IllegalStateException expected");
178177
}
179-
catch (ServletException ex) {
178+
catch (IllegalStateException ex) {
180179
// expected
181180
}
182181
}

0 commit comments

Comments
 (0)