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