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