001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet;
018
019import java.util.Map;
020
021import org.springframework.http.HttpStatus;
022import org.springframework.ui.ModelMap;
023import org.springframework.util.CollectionUtils;
024
025/**
026 * Holder for both Model and View in the web MVC framework.
027 * Note that these are entirely distinct. This class merely holds
028 * both to make it possible for a controller to return both model
029 * and view in a single return value.
030 *
031 * <p>Represents a model and view returned by a handler, to be resolved
032 * by a DispatcherServlet. The view can take the form of a String
033 * view name which will need to be resolved by a ViewResolver object;
034 * alternatively a View object can be specified directly. The model
035 * is a Map, allowing the use of multiple objects keyed by name.
036 *
037 * @author Rod Johnson
038 * @author Juergen Hoeller
039 * @author Rob Harrop
040 * @author Rossen Stoyanchev
041 * @see DispatcherServlet
042 * @see ViewResolver
043 * @see HandlerAdapter#handle
044 * @see org.springframework.web.servlet.mvc.Controller#handleRequest
045 */
046public class ModelAndView {
047
048        /** View instance or view name String */
049        private Object view;
050
051        /** Model Map */
052        private ModelMap model;
053
054        /** Optional HTTP status for the response */
055        private HttpStatus status;
056
057        /** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
058        private boolean cleared = false;
059
060
061        /**
062         * Default constructor for bean-style usage: populating bean
063         * properties instead of passing in constructor arguments.
064         * @see #setView(View)
065         * @see #setViewName(String)
066         */
067        public ModelAndView() {
068        }
069
070        /**
071         * Convenient constructor when there is no model data to expose.
072         * Can also be used in conjunction with {@code addObject}.
073         * @param viewName name of the View to render, to be resolved
074         * by the DispatcherServlet's ViewResolver
075         * @see #addObject
076         */
077        public ModelAndView(String viewName) {
078                this.view = viewName;
079        }
080
081        /**
082         * Convenient constructor when there is no model data to expose.
083         * Can also be used in conjunction with {@code addObject}.
084         * @param view View object to render
085         * @see #addObject
086         */
087        public ModelAndView(View view) {
088                this.view = view;
089        }
090
091        /**
092         * Create a new ModelAndView given a view name and a model.
093         * @param viewName name of the View to render, to be resolved
094         * by the DispatcherServlet's ViewResolver
095         * @param model Map of model names (Strings) to model objects
096         * (Objects). Model entries may not be {@code null}, but the
097         * model Map may be {@code null} if there is no model data.
098         */
099        public ModelAndView(String viewName, Map<String, ?> model) {
100                this.view = viewName;
101                if (model != null) {
102                        getModelMap().addAllAttributes(model);
103                }
104        }
105
106        /**
107         * Create a new ModelAndView given a View object and a model.
108         * <emphasis>Note: the supplied model data is copied into the internal
109         * storage of this class. You should not consider to modify the supplied
110         * Map after supplying it to this class</emphasis>
111         * @param view View object to render
112         * @param model Map of model names (Strings) to model objects
113         * (Objects). Model entries may not be {@code null}, but the
114         * model Map may be {@code null} if there is no model data.
115         */
116        public ModelAndView(View view, Map<String, ?> model) {
117                this.view = view;
118                if (model != null) {
119                        getModelMap().addAllAttributes(model);
120                }
121        }
122
123        /**
124         * Create a new ModelAndView given a view name and HTTP status.
125         * @param viewName name of the View to render, to be resolved
126         * by the DispatcherServlet's ViewResolver
127         * @param status an HTTP status code to use for the response
128         * (to be set just prior to View rendering)
129         * @since 4.3.8
130         */
131        public ModelAndView(String viewName, HttpStatus status) {
132                this.view = viewName;
133                this.status = status;
134        }
135
136        /**
137         * Create a new ModelAndView given a view name, model, and HTTP status.
138         * @param viewName name of the View to render, to be resolved
139         * by the DispatcherServlet's ViewResolver
140         * @param model Map of model names (Strings) to model objects
141         * (Objects). Model entries may not be {@code null}, but the
142         * model Map may be {@code null} if there is no model data.
143         * @param status an HTTP status code to use for the response
144         * (to be set just prior to View rendering)
145         * @since 4.3
146         */
147        public ModelAndView(String viewName, Map<String, ?> model, HttpStatus status) {
148                this.view = viewName;
149                if (model != null) {
150                        getModelMap().addAllAttributes(model);
151                }
152                this.status = status;
153        }
154
155        /**
156         * Convenient constructor to take a single model object.
157         * @param viewName name of the View to render, to be resolved
158         * by the DispatcherServlet's ViewResolver
159         * @param modelName name of the single entry in the model
160         * @param modelObject the single model object
161         */
162        public ModelAndView(String viewName, String modelName, Object modelObject) {
163                this.view = viewName;
164                addObject(modelName, modelObject);
165        }
166
167        /**
168         * Convenient constructor to take a single model object.
169         * @param view View object to render
170         * @param modelName name of the single entry in the model
171         * @param modelObject the single model object
172         */
173        public ModelAndView(View view, String modelName, Object modelObject) {
174                this.view = view;
175                addObject(modelName, modelObject);
176        }
177
178
179        /**
180         * Set a view name for this ModelAndView, to be resolved by the
181         * DispatcherServlet via a ViewResolver. Will override any
182         * pre-existing view name or View.
183         */
184        public void setViewName(String viewName) {
185                this.view = viewName;
186        }
187
188        /**
189         * Return the view name to be resolved by the DispatcherServlet
190         * via a ViewResolver, or {@code null} if we are using a View object.
191         */
192        public String getViewName() {
193                return (this.view instanceof String ? (String) this.view : null);
194        }
195
196        /**
197         * Set a View object for this ModelAndView. Will override any
198         * pre-existing view name or View.
199         */
200        public void setView(View view) {
201                this.view = view;
202        }
203
204        /**
205         * Return the View object, or {@code null} if we are using a view name
206         * to be resolved by the DispatcherServlet via a ViewResolver.
207         */
208        public View getView() {
209                return (this.view instanceof View ? (View) this.view : null);
210        }
211
212        /**
213         * Indicate whether or not this {@code ModelAndView} has a view, either
214         * as a view name or as a direct {@link View} instance.
215         */
216        public boolean hasView() {
217                return (this.view != null);
218        }
219
220        /**
221         * Return whether we use a view reference, i.e. {@code true}
222         * if the view has been specified via a name to be resolved by the
223         * DispatcherServlet via a ViewResolver.
224         */
225        public boolean isReference() {
226                return (this.view instanceof String);
227        }
228
229        /**
230         * Return the model map. May return {@code null}.
231         * Called by DispatcherServlet for evaluation of the model.
232         */
233        protected Map<String, Object> getModelInternal() {
234                return this.model;
235        }
236
237        /**
238         * Return the underlying {@code ModelMap} instance (never {@code null}).
239         */
240        public ModelMap getModelMap() {
241                if (this.model == null) {
242                        this.model = new ModelMap();
243                }
244                return this.model;
245        }
246
247        /**
248         * Return the model map. Never returns {@code null}.
249         * To be called by application code for modifying the model.
250         */
251        public Map<String, Object> getModel() {
252                return getModelMap();
253        }
254
255        /**
256         * Set the HTTP status to use for the response.
257         * <p>The response status is set just prior to View rendering.
258         * @since 4.3
259         */
260        public void setStatus(HttpStatus status) {
261                this.status = status;
262        }
263
264        /**
265         * Return the configured HTTP status for the response, if any.
266         * @since 4.3
267         */
268        public HttpStatus getStatus() {
269                return this.status;
270        }
271
272
273        /**
274         * Add an attribute to the model.
275         * @param attributeName name of the object to add to the model
276         * @param attributeValue object to add to the model (never {@code null})
277         * @see ModelMap#addAttribute(String, Object)
278         * @see #getModelMap()
279         */
280        public ModelAndView addObject(String attributeName, Object attributeValue) {
281                getModelMap().addAttribute(attributeName, attributeValue);
282                return this;
283        }
284
285        /**
286         * Add an attribute to the model using parameter name generation.
287         * @param attributeValue the object to add to the model (never {@code null})
288         * @see ModelMap#addAttribute(Object)
289         * @see #getModelMap()
290         */
291        public ModelAndView addObject(Object attributeValue) {
292                getModelMap().addAttribute(attributeValue);
293                return this;
294        }
295
296        /**
297         * Add all attributes contained in the provided Map to the model.
298         * @param modelMap a Map of attributeName -> attributeValue pairs
299         * @see ModelMap#addAllAttributes(Map)
300         * @see #getModelMap()
301         */
302        public ModelAndView addAllObjects(Map<String, ?> modelMap) {
303                getModelMap().addAllAttributes(modelMap);
304                return this;
305        }
306
307
308        /**
309         * Clear the state of this ModelAndView object.
310         * The object will be empty afterwards.
311         * <p>Can be used to suppress rendering of a given ModelAndView object
312         * in the {@code postHandle} method of a HandlerInterceptor.
313         * @see #isEmpty()
314         * @see HandlerInterceptor#postHandle
315         */
316        public void clear() {
317                this.view = null;
318                this.model = null;
319                this.cleared = true;
320        }
321
322        /**
323         * Return whether this ModelAndView object is empty,
324         * i.e. whether it does not hold any view and does not contain a model.
325         */
326        public boolean isEmpty() {
327                return (this.view == null && CollectionUtils.isEmpty(this.model));
328        }
329
330        /**
331         * Return whether this ModelAndView object is empty as a result of a call to {@link #clear}
332         * i.e. whether it does not hold any view and does not contain a model.
333         * <p>Returns {@code false} if any additional state was added to the instance
334         * <strong>after</strong> the call to {@link #clear}.
335         * @see #clear()
336         */
337        public boolean wasCleared() {
338                return (this.cleared && isEmpty());
339        }
340
341
342        /**
343         * Return diagnostic information about this model and view.
344         */
345        @Override
346        public String toString() {
347                StringBuilder sb = new StringBuilder("ModelAndView: ");
348                if (isReference()) {
349                        sb.append("reference to view with name '").append(this.view).append("'");
350                }
351                else {
352                        sb.append("materialized View is [").append(this.view).append(']');
353                }
354                sb.append("; model is ").append(this.model);
355                return sb.toString();
356        }
357
358}