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 }