1 /*
2  * Copyright 2002-2011 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.web.method.support;
18 
19 import java.util.Map;
20 
21 import org.springframework.ui.Model;
22 import org.springframework.ui.ModelMap;
23 import org.springframework.validation.support.BindingAwareModelMap;
24 import org.springframework.web.bind.support.SessionStatus;
25 import org.springframework.web.bind.support.SimpleSessionStatus;
26 
27 /**
28  * Records model and view related decisions made by
29  * {@link HandlerMethodArgumentResolver}s and
30  * {@link HandlerMethodReturnValueHandler}s during the course of invocation of
31  * a controller method.
32  *
33  * <p>The {@link #setRequestHandled} flag can be used to indicate the request
34  * has been handled directly and view resolution is not required.
35  *
36  * <p>A default {@link Model} is automatically created at instantiation.
37  * An alternate model instance may be provided via {@link #setRedirectModel}
38  * for use in a redirect scenario. When {@link #setRedirectModelScenario} is set
39  * to {@code true} signalling a redirect scenario, the {@link #getModel()}
40  * returns the redirect model instead of the default model.
41  *
42  * @author Rossen Stoyanchev
43  * @since 3.1
44  */
45 public class ModelAndViewContainer {
46 
47 	private Object view;
48 
49 	private boolean requestHandled = false;
50 
51 	private final ModelMap defaultModel = new BindingAwareModelMap();
52 
53 	private ModelMap redirectModel;
54 
55 	private boolean redirectModelScenario = false;
56 
57 	private boolean ignoreDefaultModelOnRedirect = false;
58 
59 	private final SessionStatus sessionStatus = new SimpleSessionStatus();
60 
61 	/**
62 	 * Create a new instance.
63 	 */
ModelAndViewContainer()64 	public ModelAndViewContainer() {
65 	}
66 
67 	/**
68 	 * Set a view name to be resolved by the DispatcherServlet via a ViewResolver.
69 	 * Will override any pre-existing view name or View.
70 	 */
setViewName(String viewName)71 	public void setViewName(String viewName) {
72 		this.view = viewName;
73 	}
74 
75 	/**
76 	 * Return the view name to be resolved by the DispatcherServlet via a
77 	 * ViewResolver, or {@code null} if a View object is set.
78 	 */
getViewName()79 	public String getViewName() {
80 		return (this.view instanceof String ? (String) this.view : null);
81 	}
82 
83 	/**
84 	 * Set a View object to be used by the DispatcherServlet.
85 	 * Will override any pre-existing view name or View.
86 	 */
setView(Object view)87 	public void setView(Object view) {
88 		this.view = view;
89 	}
90 
91 	/**
92 	 * Return the View object, or {@code null} if we using a view name
93 	 * to be resolved by the DispatcherServlet via a ViewResolver.
94 	 */
getView()95 	public Object getView() {
96 		return this.view;
97 	}
98 
99 	/**
100 	 * Whether the view is a view reference specified via a name to be
101 	 * resolved by the DispatcherServlet via a ViewResolver.
102 	 */
isViewReference()103 	public boolean isViewReference() {
104 		return (this.view instanceof String);
105 	}
106 
107 	/**
108 	 * Signal a scenario where the request is handled directly.
109 	 * <p>A {@link HandlerMethodReturnValueHandler} may use this flag to
110 	 * indicate the response has been fully handled and view resolution
111 	 * is not required (e.g. {@code @ResponseBody}).
112 	 * <p>A {@link HandlerMethodArgumentResolver} may also use this flag
113 	 * to indicate the presence of an argument (e.g.
114 	 * {@code ServletResponse} or {@code OutputStream}) that may lead to
115 	 * a complete response depending on the method return value.
116 	 * <p>The default value is {@code true}.
117 	 */
setRequestHandled(boolean requestHandled)118 	public void setRequestHandled(boolean requestHandled) {
119 		this.requestHandled = requestHandled;
120 	}
121 
122 	/**
123 	 * Whether the request is handled directly.
124 	 */
isRequestHandled()125 	public boolean isRequestHandled() {
126 		return this.requestHandled;
127 	}
128 
129 	/**
130 	 * Return the model to use: the "default" or the "redirect" model.
131 	 * <p>The default model is used if {@code "redirectModelScenario=false"} or
132 	 * if the redirect model is {@code null} (i.e. it wasn't declared as a
133 	 * method argument) and {@code ignoreDefaultModelOnRedirect=false}.
134 	 */
getModel()135 	public ModelMap getModel() {
136 		if (useDefaultModel()) {
137 			return this.defaultModel;
138 		}
139 		else {
140 			return (this.redirectModel != null) ? this.redirectModel : new ModelMap();
141 		}
142 	}
143 
144 	/**
145 	 * Whether to use the default model or the redirect model.
146 	 */
useDefaultModel()147 	private boolean useDefaultModel() {
148 		return !this.redirectModelScenario || ((this.redirectModel == null) && !this.ignoreDefaultModelOnRedirect);
149 	}
150 
151 	/**
152 	 * Provide a separate model instance to use in a redirect scenario.
153 	 * The provided additional model however is not used used unless
154 	 * {@link #setRedirectModelScenario(boolean)} gets set to {@code true} to signal
155 	 * a redirect scenario.
156 	 */
setRedirectModel(ModelMap redirectModel)157 	public void setRedirectModel(ModelMap redirectModel) {
158 		this.redirectModel = redirectModel;
159 	}
160 
161 	/**
162 	 * Signal the conditions are in place for using a redirect model.
163 	 * Typically that means the controller has returned a redirect instruction.
164 	 */
setRedirectModelScenario(boolean redirectModelScenario)165 	public void setRedirectModelScenario(boolean redirectModelScenario) {
166 		this.redirectModelScenario = redirectModelScenario;
167 	}
168 
169 	/**
170 	 * When set to {@code true} the default model is never used in a redirect
171 	 * scenario. So if a redirect model is not available, an empty model is
172 	 * used instead.
173 	 * <p>When set to {@code false} the default model can be used in a redirect
174 	 * scenario if a redirect model is not available.
175 	 * <p>The default setting is {@code false}.
176 	 */
setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect)177 	public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
178 		this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
179 	}
180 
181 	/**
182 	 * Return the {@link SessionStatus} instance to use that can be used to
183 	 * signal that session processing is complete.
184 	 */
getSessionStatus()185 	public SessionStatus getSessionStatus() {
186 		return sessionStatus;
187 	}
188 
189 	/**
190 	 * Add the supplied attribute to the underlying model.
191 	 * @see ModelMap#addAttribute(String, Object)
192 	 */
addAttribute(String name, Object value)193 	public ModelAndViewContainer addAttribute(String name, Object value) {
194 		getModel().addAttribute(name, value);
195 		return this;
196 	}
197 
198 	/**
199 	 * Add the supplied attribute to the underlying model.
200 	 * @see Model#addAttribute(Object)
201 	 */
addAttribute(Object value)202 	public ModelAndViewContainer addAttribute(Object value) {
203 		getModel().addAttribute(value);
204 		return this;
205 	}
206 
207 	/**
208 	 * Copy all attributes to the underlying model.
209 	 * @see ModelMap#addAllAttributes(Map)
210 	 */
addAllAttributes(Map<String, ?> attributes)211 	public ModelAndViewContainer addAllAttributes(Map<String, ?> attributes) {
212 		getModel().addAllAttributes(attributes);
213 		return this;
214 	}
215 
216 	/**
217 	 * Copy attributes in the supplied <code>Map</code> with existing objects of
218 	 * the same name taking precedence (i.e. not getting replaced).
219 	 * @see ModelMap#mergeAttributes(Map)
220 	 */
mergeAttributes(Map<String, ?> attributes)221 	public ModelAndViewContainer mergeAttributes(Map<String, ?> attributes) {
222 		getModel().mergeAttributes(attributes);
223 		return this;
224 	}
225 
226 	/**
227 	 * Remove the given attributes from the model.
228 	 */
removeAttributes(Map<String, ?> attributes)229 	public ModelAndViewContainer removeAttributes(Map<String, ?> attributes) {
230 		if (attributes != null) {
231 			for (String key : attributes.keySet()) {
232 				getModel().remove(key);
233 			}
234 		}
235 		return this;
236 	}
237 
238 	/**
239 	 * Whether the underlying model contains the given attribute name.
240 	 * @see ModelMap#containsAttribute(String)
241 	 */
containsAttribute(String name)242 	public boolean containsAttribute(String name) {
243 		return getModel().containsAttribute(name);
244 	}
245 
246 	/**
247 	 * Return diagnostic information.
248 	 */
249 	@Override
toString()250 	public String toString() {
251 		StringBuilder sb = new StringBuilder("ModelAndViewContainer: ");
252 		if (!isRequestHandled()) {
253 			if (isViewReference()) {
254 				sb.append("reference to view with name '").append(this.view).append("'");
255 			}
256 			else {
257 				sb.append("View is [").append(this.view).append(']');
258 			}
259 			if (useDefaultModel()) {
260 				sb.append("; default model ");
261 			}
262 			else {
263 				sb.append("; redirect model ");
264 			}
265 			sb.append(getModel());
266 		}
267 		else {
268 			sb.append("Request handled directly");
269 		}
270 		return sb.toString();
271 	}
272 
273 }
274