View Javadoc
1   /*
2    * Copyright 2002-2014 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 boolean ignoreDefaultModelOnRedirect = false;
48  
49  	private Object view;
50  
51  	private final ModelMap defaultModel = new BindingAwareModelMap();
52  
53  	private ModelMap redirectModel;
54  
55  	private boolean redirectModelScenario = false;
56  
57  	private final SessionStatus sessionStatus = new SimpleSessionStatus();
58  
59  	private boolean requestHandled = false;
60  
61  
62  	/**
63  	 * By default the content of the "default" model is used both during
64  	 * rendering and redirect scenarios. Alternatively controller methods
65  	 * can declare an argument of type {@code RedirectAttributes} and use
66  	 * it to provide attributes to prepare the redirect URL.
67  	 * <p>Setting this flag to {@code true} guarantees the "default" model is
68  	 * never used in a redirect scenario even if a RedirectAttributes argument
69  	 * is not declared. Setting it to {@code false} means the "default" model
70  	 * may be used in a redirect if the controller method doesn't declare a
71  	 * RedirectAttributes argument.
72  	 * <p>The default setting is {@code false}.
73  	 */
74  	public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
75  		this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
76  	}
77  
78  	/**
79  	 * Set a view name to be resolved by the DispatcherServlet via a ViewResolver.
80  	 * Will override any pre-existing view name or View.
81  	 */
82  	public void setViewName(String viewName) {
83  		this.view = viewName;
84  	}
85  
86  	/**
87  	 * Return the view name to be resolved by the DispatcherServlet via a
88  	 * ViewResolver, or {@code null} if a View object is set.
89  	 */
90  	public String getViewName() {
91  		return (this.view instanceof String ? (String) this.view : null);
92  	}
93  
94  	/**
95  	 * Set a View object to be used by the DispatcherServlet.
96  	 * Will override any pre-existing view name or View.
97  	 */
98  	public void setView(Object view) {
99  		this.view = view;
100 	}
101 
102 	/**
103 	 * Return the View object, or {@code null} if we using a view name
104 	 * to be resolved by the DispatcherServlet via a ViewResolver.
105 	 */
106 	public Object getView() {
107 		return this.view;
108 	}
109 
110 	/**
111 	 * Whether the view is a view reference specified via a name to be
112 	 * resolved by the DispatcherServlet via a ViewResolver.
113 	 */
114 	public boolean isViewReference() {
115 		return (this.view instanceof String);
116 	}
117 
118 	/**
119 	 * Return the model to use -- either the "default" or the "redirect" model.
120 	 * The default model is used if {@code redirectModelScenario=false} or
121 	 * there is no redirect model (i.e. RedirectAttributes was not declared as
122 	 * a method argument) and {@code ignoreDefaultModelOnRedirect=false}.
123 	 */
124 	public ModelMap getModel() {
125 		if (useDefaultModel()) {
126 			return this.defaultModel;
127 		}
128 		else {
129 			return (this.redirectModel != null) ? this.redirectModel : new ModelMap();
130 		}
131 	}
132 
133 	/**
134 	 * Whether to use the default model or the redirect model.
135 	 */
136 	private boolean useDefaultModel() {
137 		return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
138 	}
139 
140 	/**
141 	 * Return the "default" model created at instantiation.
142 	 * <p>In general it is recommended to use {@link #getModel()} instead which
143 	 * returns either the "default" model (template rendering) or the "redirect"
144 	 * model (redirect URL preparation). Use of this method may be needed for
145 	 * advanced cases when access to the "default" model is needed regardless,
146 	 * e.g. to save model attributes specified via {@code @SessionAttributes}.
147 	 * @return the default model, never {@code null}
148 	 */
149 	public ModelMap getDefaultModel() {
150 		return this.defaultModel;
151 	}
152 
153 	/**
154 	 * Provide a separate model instance to use in a redirect scenario.
155 	 * The provided additional model however is not used used unless
156 	 * {@link #setRedirectModelScenario(boolean)} gets set to {@code true} to signal
157 	 * a redirect scenario.
158 	 */
159 	public void setRedirectModel(ModelMap redirectModel) {
160 		this.redirectModel = redirectModel;
161 	}
162 
163 	/**
164 	 * Whether the controller has returned a redirect instruction, e.g. a
165 	 * "redirect:" prefixed view name, a RedirectView instance, etc.
166 	 */
167 	public void setRedirectModelScenario(boolean redirectModelScenario) {
168 		this.redirectModelScenario = redirectModelScenario;
169 	}
170 
171 	/**
172 	 * Return the {@link SessionStatus} instance to use that can be used to
173 	 * signal that session processing is complete.
174 	 */
175 	public SessionStatus getSessionStatus() {
176 		return this.sessionStatus;
177 	}
178 
179 	/**
180 	 * Whether the request has been handled fully within the handler, e.g.
181 	 * {@code @ResponseBody} method, and therefore view resolution is not
182 	 * necessary. This flag can also be set when controller methods declare an
183 	 * argument of type {@code ServletResponse} or {@code OutputStream}).
184 	 * <p>The default value is {@code false}.
185 	 */
186 	public void setRequestHandled(boolean requestHandled) {
187 		this.requestHandled = requestHandled;
188 	}
189 
190 	/**
191 	 * Whether the request has been handled fully within the handler.
192 	 */
193 	public boolean isRequestHandled() {
194 		return this.requestHandled;
195 	}
196 
197 	/**
198 	 * Add the supplied attribute to the underlying model.
199 	 * A shortcut for {@code getModel().addAttribute(String, Object)}.
200 	 */
201 	public ModelAndViewContainer addAttribute(String name, Object value) {
202 		getModel().addAttribute(name, value);
203 		return this;
204 	}
205 
206 	/**
207 	 * Add the supplied attribute to the underlying model.
208 	 * A shortcut for {@code getModel().addAttribute(Object)}.
209 	 */
210 	public ModelAndViewContainer addAttribute(Object value) {
211 		getModel().addAttribute(value);
212 		return this;
213 	}
214 
215 	/**
216 	 * Copy all attributes to the underlying model.
217 	 * A shortcut for {@code getModel().addAllAttributes(Map)}.
218 	 */
219 	public ModelAndViewContainer addAllAttributes(Map<String, ?> attributes) {
220 		getModel().addAllAttributes(attributes);
221 		return this;
222 	}
223 
224 	/**
225 	 * Copy attributes in the supplied {@code Map} with existing objects of
226 	 * the same name taking precedence (i.e. not getting replaced).
227 	 * A shortcut for {@code getModel().mergeAttributes(Map<String, ?>)}.
228 	 */
229 	public ModelAndViewContainer mergeAttributes(Map<String, ?> attributes) {
230 		getModel().mergeAttributes(attributes);
231 		return this;
232 	}
233 
234 	/**
235 	 * Remove the given attributes from the model.
236 	 */
237 	public ModelAndViewContainer removeAttributes(Map<String, ?> attributes) {
238 		if (attributes != null) {
239 			for (String key : attributes.keySet()) {
240 				getModel().remove(key);
241 			}
242 		}
243 		return this;
244 	}
245 
246 	/**
247 	 * Whether the underlying model contains the given attribute name.
248 	 * A shortcut for {@code getModel().containsAttribute(String)}.
249 	 */
250 	public boolean containsAttribute(String name) {
251 		return getModel().containsAttribute(name);
252 	}
253 
254 
255 	/**
256 	 * Return diagnostic information.
257 	 */
258 	@Override
259 	public String toString() {
260 		StringBuilder sb = new StringBuilder("ModelAndViewContainer: ");
261 		if (!isRequestHandled()) {
262 			if (isViewReference()) {
263 				sb.append("reference to view with name '").append(this.view).append("'");
264 			}
265 			else {
266 				sb.append("View is [").append(this.view).append(']');
267 			}
268 			if (useDefaultModel()) {
269 				sb.append("; default model ");
270 			}
271 			else {
272 				sb.append("; redirect model ");
273 			}
274 			sb.append(getModel());
275 		}
276 		else {
277 			sb.append("Request handled directly");
278 		}
279 		return sb.toString();
280 	}
281 
282 }