diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index c0eb4390886..afada195287 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -52,6 +53,7 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.util.NestedServletException; import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.WebUtils; @@ -178,6 +180,11 @@ public class DispatcherServlet extends FrameworkServlet { */ public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; + /** + * Well-known name for the FlashMapManager object in the bean factory for this namespace. + */ + public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager"; + /** * Request attribute to hold the current web application context. * Otherwise only the global web app context is obtainable by tags etc. @@ -202,7 +209,7 @@ public class DispatcherServlet extends FrameworkServlet { * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource */ public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE"; - + /** Log category to use when no mapped handler is found for a request. */ public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; @@ -269,6 +276,9 @@ public class DispatcherServlet extends FrameworkServlet { /** RequestToViewNameTranslator used by this servlet */ private RequestToViewNameTranslator viewNameTranslator; + /** FlashMapManager used by this servlet */ + private FlashMapManager flashMapManager; + /** List of ViewResolvers used by this servlet */ private List viewResolvers; @@ -414,6 +424,7 @@ public class DispatcherServlet extends FrameworkServlet { initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); + initFlashMapManager(context); } /** @@ -659,6 +670,28 @@ public class DispatcherServlet extends FrameworkServlet { } } + /** + * Initialize the {@link FlashMapManager} used by this servlet instance. + *

If no implementation is configured then we default to DefaultFlashMapManager. + */ + private void initFlashMapManager(ApplicationContext context) { + try { + this.flashMapManager = + context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); + if (logger.isDebugEnabled()) { + logger.debug("Using FlashMapManager [" + this.flashMapManager + "]"); + } + } + catch (NoSuchBeanDefinitionException ex) { + // We need to use the default. + this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class); + if (logger.isDebugEnabled()) { + logger.debug("Unable to locate FlashMapManager with name '" + + FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]"); + } + } + } + /** * Return this servlet's ThemeSource, if any; else return null. *

Default is to return the WebApplicationContext as ThemeSource, @@ -782,6 +815,8 @@ public class DispatcherServlet extends FrameworkServlet { } } + boolean flashInitialized = this.flashMapManager.requestStarted(request); + // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); @@ -792,6 +827,9 @@ public class DispatcherServlet extends FrameworkServlet { doDispatch(request, response); } finally { + if (flashInitialized) { + this.flashMapManager.requestCompleted(request); + } // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties index 933d4ed55bd..a3ec101afaf 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties @@ -20,3 +20,5 @@ org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver + +org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java new file mode 100644 index 00000000000..9dfaf6fec26 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet; + +import org.springframework.ui.ModelMap; +import org.springframework.util.Assert; + +/** + * Stores attributes that need to be made available in the next request. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class FlashMap extends ModelMap { + + private static final long serialVersionUID = 1L; + + private final String key; + + private final String keyParameterName; + + private long expirationStartTime; + + private int timeToLive; + + /** + * Create a FlashMap with a unique key. + */ + public FlashMap(String key, String keyParameterName) { + Assert.notNull("The key is required", key); + Assert.notNull("The key parameter name is required", keyParameterName); + this.key = key; + this.keyParameterName = keyParameterName; + } + + /** + * Create a FlashMap without a key. + */ + public FlashMap() { + this.key = null; + this.keyParameterName = null; + } + + /** + * Return the key assigned to this FlashMap instance; + * or {@code null} if a unique key has not been assigned. + */ + public String getKey() { + return this.key; + } + + /** + * Return the name of the request parameter to use when appending the flash + * key to a redirect URL. + */ + public String getKeyParameterName() { + return keyParameterName; + } + + /** + * Start the expiration period for this instance. After the given number of + * seconds calls to {@link #isExpired()} will return "true". + * @param timeToLive the number of seconds before flash map expires + */ + public void startExpirationPeriod(int timeToLive) { + this.expirationStartTime = System.currentTimeMillis(); + this.timeToLive = timeToLive; + } + + /** + * Whether the flash map has expired depending on the number of seconds + * elapsed since the call to {@link #startExpirationPeriod}. + */ + public boolean isExpired() { + if (this.expirationStartTime != 0) { + return (System.currentTimeMillis() - this.expirationStartTime) > this.timeToLive * 1000; + } + else { + return false; + } + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java new file mode 100644 index 00000000000..98d8778034b --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.servlet.support.RequestContextUtils; + +/** + * A strategy interface for maintaining {@link FlashMap} instances in some + * underlying storage between two requests. This is typically used when + * redirecting from one URL to another. + * + * TODO ... + * + * @author Rossen Stoyanchev + * @since 3.1 + * + * @see FlashMap + */ +public interface FlashMapManager { + + /** + * Request attribute to hold the current request FlashMap. + * @see RequestContextUtils#getFlashMap + */ + public static final String CURRENT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".CURRENT_FLASH_MAP"; + + /** + * Request attribute to hold the FlashMap from the previous request. + */ + public static final String PREVIOUS_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".PREVIOUS_FLASH_MAP"; + + /** + * Perform flash storage tasks at the start of a new request: + *

+ * + * @param request the current request + * + * @return "true" if flash storage tasks were performed; "false" otherwise + * if the {@link #CURRENT_FLASH_MAP_ATTRIBUTE} request attribute exists. + */ + boolean requestStarted(HttpServletRequest request); + + /** + * Access the current FlashMap through the {@link #CURRENT_FLASH_MAP_ATTRIBUTE} + * request attribute and if not empty, save it in the underlying storage. This + * method should be invoked after {@link #requestStarted} and if it returned "true". + */ + void requestCompleted(HttpServletRequest request); + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index 28a1ade5e60..13c47eab1ec 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -60,6 +60,7 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequ import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.support.ResponseContext; /** * An {@link AbstractHandlerMethodExceptionResolver} that supports using {@link ExceptionHandler}-annotated methods @@ -231,7 +232,9 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce } ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - exceptionHandler.invokeAndHandle(webRequest, mavContainer, ex); + ResponseContext responseContext = new ResponseContext(webRequest, mavContainer); + + exceptionHandler.invokeAndHandle(webRequest, mavContainer, ex, responseContext); if (!mavContainer.isResolveView()) { return new ModelAndView(); @@ -239,7 +242,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce else { ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel()); mav.setViewName(mavContainer.getViewName()); - if (mavContainer.getView() != null) { + if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } return mav; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index af3f5c36215..ddc15944671 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -20,7 +20,6 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; @@ -47,13 +46,10 @@ import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.DefaultDataBinderFactory; import org.springframework.web.bind.support.DefaultSessionAttributeStore; -import org.springframework.web.bind.support.FlashStatus; import org.springframework.web.bind.support.SessionAttributeStore; import org.springframework.web.bind.support.SessionStatus; -import org.springframework.web.bind.support.SimpleFlashStatus; import org.springframework.web.bind.support.SimpleSessionStatus; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -61,7 +57,6 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; -import org.springframework.web.method.annotation.FlashAttributesHandler; import org.springframework.web.method.annotation.ModelFactory; import org.springframework.web.method.annotation.SessionAttributesHandler; import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver; @@ -78,6 +73,8 @@ import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.mvc.LastModified; @@ -94,6 +91,7 @@ import org.springframework.web.servlet.mvc.method.annotation.support.ServletMode import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.support.ResponseContext; import org.springframework.web.util.WebUtils; /** @@ -149,18 +147,16 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i private final Map, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap, SessionAttributesHandler>(); - private final Map, FlashAttributesHandler> flashAttributesHandlerCache = - new ConcurrentHashMap, FlashAttributesHandler>(); - private HandlerMethodArgumentResolverComposite argumentResolvers; private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers; private HandlerMethodReturnValueHandlerComposite returnValueHandlers; - private final Map, Set> initBinderMethodCache = new ConcurrentHashMap, Set>(); + private final Map, WebDataBinderFactory> dataBinderFactoryCache = + new ConcurrentHashMap, WebDataBinderFactory>(); - private final Map, Set> modelAttributeMethodCache = new ConcurrentHashMap, Set>(); + private final Map, ModelFactory> modelFactoryCache = new ConcurrentHashMap, ModelFactory>(); /** * Create a {@link RequestMappingHandlerAdapter} instance. @@ -372,7 +368,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i argumentResolvers.addResolver(new HttpEntityMethodProcessor(messageConverters)); argumentResolvers.addResolver(new ModelMethodProcessor()); argumentResolvers.addResolver(new ErrorsMethodArgumentResolver()); - + // Default-mode resolution argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(beanFactory, true)); argumentResolvers.addResolver(new ServletModelAttributeMethodProcessor(true)); @@ -462,8 +458,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { - - if (hasSessionAttributes(handlerMethod.getBeanType())) { + + if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { // Always prevent caching in case of session attribute management. checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); } @@ -487,118 +483,106 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i } /** - * Whether the given handler type defines any handler-specific session attributes - * via {@link SessionAttributes}. + * Return the {@link SessionAttributesHandler} instance for the given + * handler type, never {@code null}. */ - private boolean hasSessionAttributes(Class handlerType) { - SessionAttributesHandler sessionAttrsHandler = null; - synchronized(this.sessionAttributesHandlerCache) { - sessionAttrsHandler = this.sessionAttributesHandlerCache.get(handlerType); - if (sessionAttrsHandler == null) { - sessionAttrsHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); - this.sessionAttributesHandlerCache.put(handlerType, sessionAttrsHandler); + private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) { + Class handlerType = handlerMethod.getBeanType(); + SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType); + if (sessionAttrHandler == null) { + synchronized(this.sessionAttributesHandlerCache) { + sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType); + if (sessionAttrHandler == null) { + sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); + this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler); + } } } - FlashAttributesHandler flashAttrsHandler = null; - synchronized(this.flashAttributesHandlerCache) { - flashAttrsHandler = this.flashAttributesHandlerCache.get(handlerType); - if (flashAttrsHandler == null) { - flashAttrsHandler = new FlashAttributesHandler(handlerType); - this.flashAttributesHandlerCache.put(handlerType, flashAttrsHandler); - } - } - return sessionAttrsHandler.hasSessionAttributes() || flashAttrsHandler.hasFlashAttributes(); + return sessionAttrHandler; } /** * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} if view resolution is required. */ - private ModelAndView invokeHandlerMethod(HttpServletRequest request, - HttpServletResponse response, - HandlerMethod handlerMethod) throws Exception { + private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, + HandlerMethod handlerMethod) throws Exception { - WebDataBinderFactory binderFactory = createDataBinderFactory(handlerMethod); - ModelFactory modelFactory = createModelFactory(handlerMethod, binderFactory); - ServletInvocableHandlerMethod requestMethod = createRequestMappingMethod(handlerMethod, binderFactory); - ServletWebRequest webRequest = new ServletWebRequest(request, response); - SessionStatus sessionStatus = new SimpleSessionStatus(); - FlashStatus flashStatus = new SimpleFlashStatus(); + + ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod); + ModelFactory modelFactory = getModelFactory(handlerMethod); + FlashMap flashMap = (FlashMap) request.getAttribute(FlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); - modelFactory.initModel(webRequest, mavContainer, requestMethod); + mavContainer.addAllAttributes(flashMap); + modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); - requestMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus, flashStatus); - modelFactory.updateModel(webRequest, mavContainer, sessionStatus, flashStatus); + SessionStatus sessionStatus = new SimpleSessionStatus(); + ResponseContext responseContext = new ResponseContext(webRequest, mavContainer); + requestMappingMethod.invokeAndHandle(webRequest, mavContainer, sessionStatus, responseContext); + modelFactory.updateModel(webRequest, mavContainer, sessionStatus); + if (!mavContainer.isResolveView()) { return null; } else { ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel()); mav.setViewName(mavContainer.getViewName()); - if (mavContainer.getView() != null) { + if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } return mav; } } - private WebDataBinderFactory createDataBinderFactory(HandlerMethod handlerMethod) { - List initBinderMethods = new ArrayList(); - + private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod) { + ServletInvocableHandlerMethod requestMappingMethod = + new ServletInvocableHandlerMethod(handlerMethod.getBean(), handlerMethod.getMethod()); + requestMappingMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); + requestMappingMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); + requestMappingMethod.setDataBinderFactory(getDataBinderFactory(handlerMethod)); + requestMappingMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); + return requestMappingMethod; + } + + private ModelFactory getModelFactory(HandlerMethod handlerMethod) { + SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); + WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); Class handlerType = handlerMethod.getBeanType(); - Set binderMethods = initBinderMethodCache.get(handlerType); - if (binderMethods == null) { - binderMethods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS); - initBinderMethodCache.put(handlerType, binderMethods); + ModelFactory modelFactory = this.modelFactoryCache.get(handlerType); + if (modelFactory == null) { + List attrMethods = new ArrayList(); + for (Method method : HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS)) { + InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method); + attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); + attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); + attrMethod.setDataBinderFactory(binderFactory); + attrMethods.add(attrMethod); + } + modelFactory = new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); + this.modelFactoryCache.put(handlerType, modelFactory); } - - for (Method method : binderMethods) { - Object bean = handlerMethod.getBean(); - InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method); - binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers); - binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer)); - binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); - initBinderMethods.add(binderMethod); - } - - return new ServletRequestDataBinderFactory(initBinderMethods, this.webBindingInitializer); + return modelFactory; } - private ModelFactory createModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { - List modelAttrMethods = new ArrayList(); - + private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) { Class handlerType = handlerMethod.getBeanType(); - Set attributeMethods = modelAttributeMethodCache.get(handlerType); - if (attributeMethods == null) { - attributeMethods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS); - modelAttributeMethodCache.put(handlerType, attributeMethods); + WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType); + if (binderFactory == null) { + List binderMethods = new ArrayList(); + for (Method method : HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS)) { + InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method); + binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers); + binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer)); + binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); + binderMethods.add(binderMethod); + } + binderFactory = new ServletRequestDataBinderFactory(binderMethods, this.webBindingInitializer); + this.dataBinderFactoryCache.put(handlerType, binderFactory); } - - for (Method method : attributeMethods) { - InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method); - attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); - attrMethod.setDataBinderFactory(binderFactory); - attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); - modelAttrMethods.add(attrMethod); - } - - SessionAttributesHandler sessionAttrsHandler = sessionAttributesHandlerCache.get(handlerType); - FlashAttributesHandler flashAttrsHandler = flashAttributesHandlerCache.get(handlerType); - - return new ModelFactory(modelAttrMethods, binderFactory, sessionAttrsHandler, flashAttrsHandler); - } - - private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod, - WebDataBinderFactory binderFactory) { - Method method = handlerMethod.getMethod(); - ServletInvocableHandlerMethod requestMethod = new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); - requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); - requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); - requestMethod.setDataBinderFactory(binderFactory); - requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); - return requestMethod; + return binderFactory; } /** diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java index c3e4ba0b1df..f6316004ff6 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java @@ -17,7 +17,6 @@ package org.springframework.web.servlet.mvc.method.annotation.support; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.List; import org.springframework.beans.BeanUtils; @@ -84,9 +83,11 @@ public class DefaultMethodReturnValueHandler implements HandlerMethodReturnValue ExtendedModelMap model = (ExtendedModelMap) mavContainer.getModel(); ModelAndView mav = resolver.resolveModelAndView(method, handlerType, returnValue, model, request); if (mav != ModelAndViewResolver.UNRESOLVED) { - mavContainer.setView(mav.getView()); - mavContainer.setViewName(mav.getViewName()); mavContainer.addAllAttributes(mav.getModel()); + mavContainer.setViewName(mav.getViewName()); + if (!mav.isReference()) { + mavContainer.setView(mav.getView()); + } return; } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/RedirectResponse.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/RedirectResponse.java new file mode 100644 index 00000000000..3998c844a49 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/RedirectResponse.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.support; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.support.RequestContextUtils; + +/** + * Provides annotated controller methods with convenience methods for setting + * up response with a view name that will result in a redirect. + * + *

An instance of this class is obtained via {@link ResponseContext#redirect}. + * + * @author Keith Donald + * @author Rossen Stoyanchev + * + * @since 3.1 + */ +public class RedirectResponse { + + private final NativeWebRequest webRequest; + + private final ModelAndViewContainer mavContainer; + + RedirectResponse(NativeWebRequest webRequest, ModelAndViewContainer mavContainer) { + this.webRequest = webRequest; + this.mavContainer = mavContainer; + } + + /** + * Add a URI template variable to use to expand the URI template into a URL. + *

Note: URI template variables from the current + * request are automatically used when expanding the redirect URI template. + * They don't need to be added explicitly here. + */ + public RedirectResponse uriVariable(String name, Object value) { + this.mavContainer.addAttribute(name, value); + return this; + } + + /** + * Add a URI template variable to use to expand the URI template into a URL. + * The name of the variable is selected using a + * {@link org.springframework.core.Conventions#getVariableName generated name}. + *

Note: URI template variables from the current + * request are automatically used when expanding the redirect URI template. + * They don't need to be added explicitly here. + */ + public RedirectResponse uriVariable(Object value) { + this.mavContainer.addAttribute(value); + return this; + } + + /** + * Add a query parameter to append to the redirect URL. + */ + public RedirectResponse queryParam(String name, Object value) { + this.mavContainer.addAttribute(name, value); + return this; + } + + /** + * Add a query parameter to append to the redirect URL. + * The name of the parameter is selected using a + * {@link org.springframework.core.Conventions#getVariableName generated name}. + */ + public RedirectResponse queryParam(Object value) { + this.mavContainer.addAttribute(value); + return this; + } + + /** + * Add a flash attribute to save and make available in the model of the + * target controller method after the redirect. + */ + public RedirectResponse flashAttribute(String name, Object value) { + getFlashMap().addAttribute(name, value); + return this; + } + + /** + * Add a flash attribute to save and make available in the model of the + * target controller method after the redirect. + * The name of the attribute is selected using a + * {@link org.springframework.core.Conventions#getVariableName generated name}. + */ + public RedirectResponse flashAttribute(Object value) { + getFlashMap().addAttribute(value); + return this; + } + + private FlashMap getFlashMap() { + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + return RequestContextUtils.getFlashMap(servletRequest); + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/ResponseContext.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/ResponseContext.java new file mode 100644 index 00000000000..ff2ba35f316 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/ResponseContext.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.support; + +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Provides annotated controllers with convenience methods for setting up view + * resolution. + * + * @author Keith Donald + * @author Rossen Stoyanchev + * + * @since 3.1 + */ +public class ResponseContext { + + private final NativeWebRequest webRequest; + + private final ModelAndViewContainer mavContainer; + + public ResponseContext(NativeWebRequest webRequest, ModelAndViewContainer mavContainer) { + this.webRequest = webRequest; + this.mavContainer = mavContainer; + } + + /** + * Set up view resolution based on the given view name and the implicit model + * of the current request. + */ + public ViewResponse view(String viewName) { + this.mavContainer.setViewName(viewName); + return new ViewResponse(this.mavContainer); + } + + /** + * Set up view resolution for a redirect. This method clears the implicit + * model. Use convenience methods on the returned {@link RedirectResponse} + * instance to add URI variables, query parameters, and flash attributes + * as necessary. + * @param redirectUri a URI template either relative to the current URL or + * absolute; do not prefix with "redirect:" + */ + public RedirectResponse redirect(String redirectUri) { + if (!redirectUri.startsWith("redirect:")) { + redirectUri = "redirect:" + redirectUri; + } + this.mavContainer.getModel().clear(); + this.mavContainer.setViewName(redirectUri); + return new RedirectResponse(this.webRequest, this.mavContainer); + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/ViewResponse.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/ViewResponse.java new file mode 100644 index 00000000000..140abd6c669 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/support/ViewResponse.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.support; + +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Provides annotated controller methods with convenience methods for setting + * up a response with a view name that does not have the "redirect:" prefix . + * + *

An instance of this class is obtained via {@link ResponseContext#view}. + * + * @author Keith Donald + * @author Rossen Stoyanchev + * + * @since 3.1 + */ +public class ViewResponse { + + private final ModelAndViewContainer mavContainer; + + ViewResponse(ModelAndViewContainer mavContainer) { + this.mavContainer = mavContainer; + } + + public ViewResponse attribute(String attributeName, Object attributeValue) { + this.mavContainer.addAttribute(attributeName, attributeValue); + return this; + } + + public ViewResponse attribute(Object attributeValue) { + this.mavContainer.addAttribute(attributeValue); + return this; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java new file mode 100644 index 00000000000..68cbf0af6e5 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/DefaultFlashMapManager.java @@ -0,0 +1,200 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.support; + +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.FlashMapManager; + +/** + * A default implementation that saves and retrieves FlashMap instances to and + * from the HTTP session. + * + * @author Rossen Stoyanchev + * @since 3.1 + */ +public class DefaultFlashMapManager implements FlashMapManager { + + static final String FLASH_MAPS_SESSION_ATTRIBUTE = DefaultFlashMapManager.class + ".FLASH_MAPS"; + + private boolean useUniqueFlashKey = true; + + private String flashKeyParameterName = "_flashKey"; + + private int flashTimeout = 180; + + private static final Random random = new Random(); + + /** + * Whether each FlashMap instance should be stored with a unique key. + * The unique key needs to be passed as a parameter in the redirect URL + * and then used to look up the FlashMap instance avoiding potential + * issues with concurrent requests. + *

The default setting is "true". + *

When set to "false" only one FlashMap is maintained making it + * possible for a second concurrent request (e.g. via Ajax) to "consume" + * the FlashMap inadvertently. + */ + public void setUseUniqueFlashKey(boolean useUniqueFlashKey) { + this.useUniqueFlashKey = useUniqueFlashKey; + } + + /** + * Customize the name of the request parameter to be appended to the + * redirect URL when using a unique flash key. + *

The default value is "_flashKey". + */ + public void setFlashKeyParameterName(String parameterName) { + this.flashKeyParameterName = parameterName; + } + + /** + * The amount of time in seconds after a request has completed processing + * and before a FlashMap is considered expired. + * The default value is 180. + */ + public void setFlashMapTimeout(int flashTimeout) { + this.flashTimeout = flashTimeout; + } + + /** + * {@inheritDoc} + * + *

This method never creates an HTTP session. The current FlashMap is + * exposed as a request attribute only and is not saved in the session + * until {@link #requestCompleted}. + */ + public boolean requestStarted(HttpServletRequest request) { + if (request.getAttribute(CURRENT_FLASH_MAP_ATTRIBUTE) != null) { + return false; + } + + FlashMap currentFlashMap = + this.useUniqueFlashKey ? + new FlashMap(createFlashKey(request), this.flashKeyParameterName) : new FlashMap(); + request.setAttribute(CURRENT_FLASH_MAP_ATTRIBUTE, currentFlashMap); + + FlashMap previousFlashMap = lookupPreviousFlashMap(request); + if (previousFlashMap != null) { + for (String name : previousFlashMap.keySet()) { + if (request.getAttribute(name) == null) { + request.setAttribute(name, previousFlashMap.get(name)); + } + } + // For exposing flash attributes in other places (e.g. annotated controllers) + request.setAttribute(PREVIOUS_FLASH_MAP_ATTRIBUTE, previousFlashMap); + } + + // Check and remove expired instances + Map allFlashMaps = retrieveAllFlashMaps(request, false); + if (allFlashMaps != null && !allFlashMaps.isEmpty()) { + Iterator iterator = allFlashMaps.values().iterator(); + while (iterator.hasNext()) { + if (iterator.next().isExpired()) { + iterator.remove(); + } + } + } + + return true; + } + + /** + * Create a unique flash key. The default implementation uses {@link Random}. + * @return the unique key; never {@code null}. + */ + protected String createFlashKey(HttpServletRequest request) { + return String.valueOf(random.nextInt()); + } + + /** + * Return the FlashMap from the previous request, if available. + * If {@link #useUniqueFlashKey} is "true", a flash key parameter is + * expected to be in the request. Otherwise there can be only one + * FlashMap instance to return. + * + * @return the FlashMap from the previous request; or {@code null} if none. + */ + private FlashMap lookupPreviousFlashMap(HttpServletRequest request) { + Map flashMaps = retrieveAllFlashMaps(request, false); + if (flashMaps != null && !flashMaps.isEmpty()) { + if (this.useUniqueFlashKey) { + String key = request.getParameter(this.flashKeyParameterName); + if (key != null) { + return flashMaps.remove(key); + } + } + else { + String key = flashMaps.keySet().iterator().next(); + return flashMaps.remove(key); + } + } + return null; + } + + /** + * Retrieve all FlashMap instances from the HTTP session in a thread-safe way. + * @param request the current request + * @param allowCreate whether to create and the FlashMap container if not found + * @return a Map with all stored FlashMap instances; or {@code null} + */ + @SuppressWarnings("unchecked") + private Map retrieveAllFlashMaps(HttpServletRequest request, boolean allowCreate) { + HttpSession session = request.getSession(allowCreate); + if (session == null) { + return null; + } + Map result = (Map) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE); + if (result == null && allowCreate) { + synchronized (DefaultFlashMapManager.class) { + result = (Map) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE); + if (result == null) { + result = new ConcurrentHashMap(5); + session.setAttribute(FLASH_MAPS_SESSION_ATTRIBUTE, result); + } + } + } + return result; + } + + /** + * {@inheritDoc} + * + *

The HTTP session is not created if the current FlashMap instance is empty. + */ + public void requestCompleted(HttpServletRequest request) { + FlashMap flashMap = (FlashMap) request.getAttribute(CURRENT_FLASH_MAP_ATTRIBUTE); + if (flashMap == null) { + throw new IllegalStateException( + "Did not find current FlashMap exposed as request attribute " + CURRENT_FLASH_MAP_ATTRIBUTE); + } + if (!flashMap.isEmpty()) { + Map allFlashMaps = retrieveAllFlashMaps(request, true); + flashMap.startExpirationPeriod(this.flashTimeout); + String key = this.useUniqueFlashKey ? flashMap.getKey() : "flashMap"; + allFlashMaps.put(key, flashMap); + } + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java index 8859e88a7c3..4f4575e01c3 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet.support; import java.util.Locale; + import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -26,6 +27,8 @@ import org.springframework.ui.context.ThemeSource; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMapManager; +import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.ThemeResolver; @@ -152,4 +155,13 @@ public abstract class RequestContextUtils { } } + /** + * Retrieves the flash map to use for the current request. + * @param request current HTTP request + * @return the flash map for the current request; never {@code null}. + */ + public static FlashMap getFlashMap(HttpServletRequest request) { + return (FlashMap) request.getAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE); + } + } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java index cc85b714852..2393a7b4104 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/RedirectView.java @@ -30,14 +30,19 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.BeanUtils; import org.springframework.http.HttpStatus; +import org.springframework.ui.ModelMap; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; +import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.View; +import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UriUtils; import org.springframework.web.util.WebUtils; @@ -76,6 +81,7 @@ import org.springframework.web.util.WebUtils; * @author Colin Sampaleanu * @author Sam Brannen * @author Arjen Poutsma + * @author Rossen Stoyanchev * @see #setContextRelative * @see #setHttp10Compatible * @see #setExposeModelAttributes @@ -262,6 +268,14 @@ public class RedirectView extends AbstractUrlBasedView { appendQueryProperties(targetUrl, model, enc); } + FlashMap flashMap = RequestContextUtils.getFlashMap(request); + if (flashMap != null && !flashMap.isEmpty()) { + if (flashMap.getKey() != null) { + ModelMap queryParam = new ModelMap(flashMap.getKeyParameterName(), flashMap.getKey()); + appendQueryProperties(targetUrl, queryParam, enc); + } + } + return targetUrl.toString(); } @@ -323,7 +337,7 @@ public class RedirectView extends AbstractUrlBasedView { } // If there aren't already some parameters, we need a "?". - boolean first = (getUrl().indexOf('?') < 0); + boolean first = (targetUrl.toString().indexOf('?') < 0); for (Map.Entry entry : queryProperties(model).entrySet()) { Object rawValue = entry.getValue(); Iterator valueIter; diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/FlashAttributesServletTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/FlashAttributesServletTests.java deleted file mode 100644 index 99311da52f6..00000000000 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/FlashAttributesServletTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.servlet.mvc.method.annotation; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.util.Locale; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.junit.Test; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.FlashAttributes; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.support.FlashStatus; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.method.annotation.FlashAttributesHandler; -import org.springframework.web.servlet.View; -import org.springframework.web.servlet.ViewResolver; - -/** - * Test controllers with @{@link FlashAttributes} through the DispatcherServlet. - * - * @author Rossen Stoyanchev - */ -public class FlashAttributesServletTests extends AbstractServletHandlerMethodTests { - - private static final String MESSAGE_KEY = "message"; - - @Test - public void successMessage() throws Exception { - - initServletWithModelExposingViewResolver(MessageController.class); - - MockHttpServletRequest req = new MockHttpServletRequest("GET", "/message"); - MockHttpServletResponse res = new MockHttpServletResponse(); - getServlet().service(req, res); - - assertEquals(200, res.getStatus()); - assertNull(getModelAttribute(req, MESSAGE_KEY)); - assertNull(getFlashAttribute(req, MESSAGE_KEY)); - - req.setMethod("POST"); - getServlet().service(req, res); - - assertEquals(200, res.getStatus()); - assertEquals("Yay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText()); - assertEquals("Yay!", ((Message) getFlashAttribute(req, MESSAGE_KEY)).getText()); - - req.setMethod("GET"); - getServlet().service(req, res); - - assertEquals(200, res.getStatus()); - assertEquals("Yay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText()); - assertNull(getFlashAttribute(req, MESSAGE_KEY)); - } - - @Test - public void successMessageAcrossControllers() throws Exception { - - initServletWithModelExposingViewResolver(MessageController.class, SecondMessageController.class); - - MockHttpServletRequest req = new MockHttpServletRequest("POST", "/message"); - MockHttpServletResponse res = new MockHttpServletResponse(); - getServlet().service(req, res); - - req.setParameter("another", "true"); - getServlet().service(req, res); - - assertEquals(200, res.getStatus()); - assertEquals("Nay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText()); - assertEquals("Nay!", ((Message) getFlashAttribute(req, MESSAGE_KEY)).getText()); - - req.setMethod("GET"); - req.setRequestURI("/second/message"); - getServlet().service(req, res); - - assertEquals(200, res.getStatus()); - assertEquals("Nay!", ((Message) getModelAttribute(req, MESSAGE_KEY)).getText()); - assertNull(getFlashAttribute(req, MESSAGE_KEY)); - } - - @Controller - @FlashAttributes("message") - static class MessageController { - - @RequestMapping(value="/message", method=RequestMethod.GET) - public void message(Model model) { - } - - @RequestMapping(value="/message", method=RequestMethod.POST) - public String sendMessage(Model model, FlashStatus status) { - status.setActive(); - model.addAttribute(Message.success("Yay!")); - return "redirect:/message"; - } - - @RequestMapping(value="/message", method=RequestMethod.POST, params="another") - public String sendMessageToSecondController(Model model, FlashStatus status) { - status.setActive(); - model.addAttribute(Message.error("Nay!")); - return "redirect:/second/message"; - } - } - - @Controller - static class SecondMessageController { - - @RequestMapping(value="/second/message", method=RequestMethod.GET) - public void message(Model model) { - } - } - - private static class Message { - - private final MessageType type; - - private final String text; - - private Message(MessageType type, String text) { - this.type = type; - this.text = text; - } - - public static Message success(String text) { - return new Message(MessageType.success, text); - } - - public static Message error(String text) { - return new Message(MessageType.error, text); - } - - public MessageType getType() { - return type; - } - - public String getText() { - return text; - } - - public String toString() { - return type + ": " + text; - } - - } - - private static enum MessageType { - info, success, warning, error - } - - @SuppressWarnings("unchecked") - private Object getModelAttribute(MockHttpServletRequest req, String key) { - Map model = (Map) req.getAttribute(ModelExposingViewResolver.REQUEST_ATTRIBITE_MODEL); - return model.get(key); - } - - @SuppressWarnings("unchecked") - private Object getFlashAttribute(MockHttpServletRequest req, String key) { - String flashAttributesKey = FlashAttributesHandler.FLASH_ATTRIBUTES_SESSION_KEY; - Map attrs = (Map) req.getSession().getAttribute(flashAttributesKey); - return (attrs != null) ? attrs.get(key) : null; - } - - private WebApplicationContext initServletWithModelExposingViewResolver(Class... controllerClasses) - throws ServletException { - - return initServlet(new ApplicationContextInitializer() { - public void initialize(GenericWebApplicationContext wac) { - wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(ModelExposingViewResolver.class)); - } - }, controllerClasses); - } - - static class ModelExposingViewResolver implements ViewResolver { - - static String REQUEST_ATTRIBITE_MODEL = "ModelExposingViewResolver.model"; - - public View resolveViewName(final String viewName, Locale locale) throws Exception { - return new View() { - public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { - request.setAttribute(REQUEST_ATTRIBITE_MODEL, model); - } - public String getContentType() { - return null; - } - }; - } - } - -} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index 12ff2acf5bd..c58bf0fda5a 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -133,11 +133,14 @@ import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.multipart.support.StringMultipartFileEditor; +import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter; +import org.springframework.web.servlet.mvc.method.support.ResponseContext; +import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.view.InternalResourceViewResolver; /** @@ -1452,7 +1455,46 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertEquals("application/json", response.getHeader("Content-Type")); assertEquals("homeJson", response.getContentAsString()); } - + + @Test + public void flashAttribute() throws Exception { + initServletWithControllers(MessageController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/messages"); + HttpSession session = request.getSession(); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + + // POST -> bind error + getServlet().service(request, response); + + assertEquals(200, response.getStatus()); + assertEquals("messages/new", response.getForwardedUrl()); + assertTrue(RequestContextUtils.getFlashMap(request).isEmpty()); + + // POST -> success + request = new MockHttpServletRequest("POST", "/messages"); + request.setSession(session); + request.addParameter("name", "Jeff"); + response = new MockHttpServletResponse(); + getServlet().service(request, response); + + FlashMap flashMap = RequestContextUtils.getFlashMap(request); + + assertNotNull(flashMap); + assertEquals(200, response.getStatus()); + assertEquals("/messages/1?name=value&_flashKey=" + flashMap.getKey(), response.getRedirectedUrl()); + + // GET after POST + request = new MockHttpServletRequest("GET", "/messages/1"); + request.setSession(session); + request.setParameter("_flashKey", String.valueOf(flashMap.getKey())); + response = new MockHttpServletResponse(); + getServlet().service(request, response); + + assertEquals(200, response.getStatus()); + assertEquals("Got: yay!", response.getContentAsString()); + } /* * Controllers @@ -2761,6 +2803,32 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl return "homeJson"; } } + + @Controller + static class MessageController { + + @InitBinder + public void initBinder(WebDataBinder dataBinder) { + dataBinder.setRequiredFields("name"); + } + + @RequestMapping(value = "/messages/{id}", method = RequestMethod.GET) + public void message(ModelMap model, Writer writer) throws IOException { + writer.write("Got: " + model.get("successMessage")); + } + + @RequestMapping(value = "/messages", method = RequestMethod.POST) + public void sendMessage(TestBean testBean, BindingResult result, ResponseContext responseContext) { + if (result.hasErrors()) { + responseContext.view("messages/new"); + } + else { + responseContext.redirect("/messages/{id}").uriVariable("id", "1").queryParam("name", "value") + .flashAttribute("successMessage", "yay!"); + } + } + } + // Test cases deleted from the original SevletAnnotationControllerTests: diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java index 8cc23867191..b29e8a701a6 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandlerTests.java @@ -56,7 +56,7 @@ public class DefaultMethodReturnValueHandlerTests { public void setUp() { mavResolvers = new ArrayList(); handler = new DefaultMethodReturnValueHandler(mavResolvers); - mavContainer = new ModelAndViewContainer(new ExtendedModelMap()); + mavContainer = new ModelAndViewContainer(); request = new ServletWebRequest(new MockHttpServletRequest()); } @@ -69,7 +69,7 @@ public class DefaultMethodReturnValueHandlerTests { handler.handleReturnValue(testBean, testBeanType, mavContainer, request); assertEquals("viewName", mavContainer.getViewName()); - assertSame(testBean, mavContainer.getAttribute("modelAttrName")); + assertSame(testBean, mavContainer.getModel().get("modelAttrName")); assertTrue(mavContainer.isResolveView()); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java index 088e3e9810e..f760024a1a3 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/ViewMethodReturnValueHandlerTests.java @@ -26,12 +26,9 @@ import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.ui.ExtendedModelMap; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.View; -import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler; -import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler; import org.springframework.web.servlet.view.InternalResourceView; /** @@ -50,7 +47,7 @@ public class ViewMethodReturnValueHandlerTests { @Before public void setUp() { handler = new ViewMethodReturnValueHandler(); - mavContainer = new ModelAndViewContainer(new ExtendedModelMap()); + mavContainer = new ModelAndViewContainer(); webRequest = new ServletWebRequest(new MockHttpServletRequest()); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java new file mode 100644 index 00000000000..2f7d3afdbf7 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/support/DefaultFlashMapManagerTests.java @@ -0,0 +1,174 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.FlashMapManager; + +/** + * Test fixture for {@link DefaultFlashMapManager} tests. + * + * @author Rossen Stoyanchev + */ +public class DefaultFlashMapManagerTests { + + private DefaultFlashMapManager flashMapManager; + + private MockHttpServletRequest request; + + @Before + public void setup() { + this.flashMapManager = new DefaultFlashMapManager(); + this.request = new MockHttpServletRequest(); + } + + @Test + public void requestAlreadyStarted() { + request.setAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, new FlashMap()); + boolean actual = this.flashMapManager.requestStarted(this.request); + + assertFalse(actual); + } + + @Test + public void createFlashMap() { + boolean actual = this.flashMapManager.requestStarted(this.request); + FlashMap flashMap = RequestContextUtils.getFlashMap(this.request); + + assertTrue(actual); + assertNotNull(flashMap); + assertNotNull(flashMap.getKey()); + assertEquals("_flashKey", flashMap.getKeyParameterName()); + } + + @Test + public void createFlashMapWithoutKey() { + this.flashMapManager.setUseUniqueFlashKey(false); + boolean actual = this.flashMapManager.requestStarted(this.request); + FlashMap flashMap = RequestContextUtils.getFlashMap(this.request); + + assertTrue(actual); + assertNotNull(flashMap); + assertNull(flashMap.getKey()); + assertNull(flashMap.getKeyParameterName()); + } + + @Test + public void lookupPreviousFlashMap() { + FlashMap flashMap = new FlashMap("key", "_flashKey"); + flashMap.put("name", "value"); + Map allFlashMaps = new HashMap(); + allFlashMaps.put(flashMap.getKey(), flashMap); + + this.request.getSession().setAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE, allFlashMaps); + this.request.addParameter("_flashKey", flashMap.getKey()); + this.flashMapManager.requestStarted(this.request); + + assertSame(flashMap, request.getAttribute(DefaultFlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE)); + assertEquals("value", request.getAttribute("name")); + } + + @Test + public void lookupPreviousFlashMapWithoutKey() { + Map allFlashMaps = new HashMap(); + request.getSession().setAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE, allFlashMaps); + + FlashMap flashMap = new FlashMap(); + flashMap.put("name", "value"); + allFlashMaps.put("key", flashMap); + + this.flashMapManager.setUseUniqueFlashKey(false); + this.flashMapManager.requestStarted(this.request); + + assertSame(flashMap, this.request.getAttribute(DefaultFlashMapManager.PREVIOUS_FLASH_MAP_ATTRIBUTE)); + assertEquals("value", this.request.getAttribute("name")); + } + + @SuppressWarnings("static-access") + @Test + public void removeExpired() throws InterruptedException { + FlashMap[] flashMapArray = new FlashMap[5]; + flashMapArray[0] = new FlashMap("key0", "_flashKey"); + flashMapArray[1] = new FlashMap("key1", "_flashKey"); + flashMapArray[2] = new FlashMap("key2", "_flashKey"); + flashMapArray[3] = new FlashMap("key3", "_flashKey"); + flashMapArray[4] = new FlashMap("key4", "_flashKey"); + + Map allFlashMaps = new HashMap(); + for (FlashMap flashMap : flashMapArray) { + allFlashMaps.put(flashMap.getKey(), flashMap); + } + + flashMapArray[1].startExpirationPeriod(0); + flashMapArray[3].startExpirationPeriod(0); + + Thread.currentThread().sleep(5); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.getSession().setAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE, allFlashMaps); + request.setParameter("_flashKey", "key0"); + this.flashMapManager.requestStarted(request); + + assertEquals(2, allFlashMaps.size()); + assertNotNull(allFlashMaps.get("key2")); + assertNotNull(allFlashMaps.get("key4")); + } + + @SuppressWarnings({ "unchecked", "static-access" }) + @Test + public void saveFlashMap() throws InterruptedException { + FlashMap flashMap = new FlashMap("key", "_flashKey"); + flashMap.put("name", "value"); + request.setAttribute(DefaultFlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap); + + this.flashMapManager.setFlashMapTimeout(0); + this.flashMapManager.requestCompleted(this.request); + + Thread.currentThread().sleep(1); + + String sessionKey = DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE; + Map allFlashMaps = (Map) this.request.getSession().getAttribute(sessionKey); + + assertSame(flashMap, allFlashMaps.get("key")); + assertTrue(flashMap.isExpired()); + } + + @Test + public void saveEmptyFlashMap() throws InterruptedException { + FlashMap flashMap = new FlashMap("key", "_flashKey"); + request.setAttribute(DefaultFlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap); + + this.flashMapManager.setFlashMapTimeout(0); + this.flashMapManager.requestCompleted(this.request); + + assertNull(this.request.getSession().getAttribute(DefaultFlashMapManager.FLASH_MAPS_SESSION_ATTRIBUTE)); + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java index 80fdeb0359a..8cb68d4da16 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/RedirectViewTests.java @@ -32,6 +32,8 @@ import org.springframework.beans.TestBean; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.View; import org.springframework.web.util.WebUtils; @@ -90,6 +92,35 @@ public class RedirectViewTests { assertEquals(201, response.getStatus()); assertEquals("http://url.somewhere.com", response.getHeader("Location")); } + + @Test + public void flashMap() throws Exception { + RedirectView rv = new RedirectView(); + rv.setUrl("http://url.somewhere.com"); + rv.setHttp10Compatible(false); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + FlashMap flashMap = new FlashMap("key", "_flashKey"); + flashMap.put("name", "value"); + request.setAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap); + rv.render(new HashMap(), request, response); + assertEquals(303, response.getStatus()); + assertEquals("http://url.somewhere.com?_flashKey=key", response.getHeader("Location")); + } + + @Test + public void emptyFlashMap() throws Exception { + RedirectView rv = new RedirectView(); + rv.setUrl("http://url.somewhere.com"); + rv.setHttp10Compatible(false); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + FlashMap flashMap = new FlashMap("key", "_flashKey"); + request.setAttribute(FlashMapManager.CURRENT_FLASH_MAP_ATTRIBUTE, flashMap); + rv.render(new HashMap(), request, response); + assertEquals(303, response.getStatus()); + assertEquals("http://url.somewhere.com", response.getHeader("Location")); + } @Test public void emptyMap() throws Exception { diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/FlashAttributes.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/FlashAttributes.java deleted file mode 100644 index 32aa8bdcf6a..00000000000 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/FlashAttributes.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.bind.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation that indicates what attributes should be stored in the session or in - * some conversational storage in order to survive a client-side redirect. - * - * TODO ... - * - * @author Rossen Stoyanchev - * @since 3.1 - * - * @see org.springframework.web.bind.support.FlashStatus - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -public @interface FlashAttributes { - - /** - * The names of flash attributes in the model to be stored. - * - * TODO ... - */ - String[] value() default {}; - - /** - * TODO ... - * - */ - Class[] types() default {}; - -} diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/FlashStatus.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/FlashStatus.java deleted file mode 100644 index b2ce47b370b..00000000000 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/support/FlashStatus.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.bind.support; - -import org.springframework.web.bind.annotation.FlashAttributes; - - -/** - * Simple interface to pass into controller methods to allow them to activate - * a mode in which model attributes identified as "flash attributes" are - * temporarily stored in the session to make them available to the next - * request. The most common scenario is a client-side redirect. - * - *

In active mode, model attributes that match the attribute names or - * types declared via @{@link FlashAttributes} are saved in the session. - * On the next request, any flash attributes found in the session are - * automatically added to the model of the target controller method and - * are also cleared from the session. - * - * TODO ... - * - * @author Rossen Stoyanchev - * @since 3.1 - */ -public interface FlashStatus { - - /** - * TODO ... - */ - void setActive(); - - /** - * TODO ... - */ - boolean isActive(); - -} diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/SimpleFlashStatus.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/SimpleFlashStatus.java deleted file mode 100644 index 0f8c4b5f088..00000000000 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/support/SimpleFlashStatus.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.bind.support; - -/** - * TODO ... - * - * @author Rossen Stoyanchev - * @since 3.1 - */ -public class SimpleFlashStatus implements FlashStatus { - - private boolean active = false; - - public void setActive() { - this.active = true; - } - - public boolean isActive() { - return this.active; - } - -} diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/FlashAttributesHandler.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/FlashAttributesHandler.java deleted file mode 100644 index 759293de97c..00000000000 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/FlashAttributesHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2002-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.method.annotation; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.web.bind.annotation.FlashAttributes; -import org.springframework.web.context.request.WebRequest; - -/** - * Manages flash attributes declared via @{@link FlashAttributes}. - * - * TODO ... - * - * @author Rossen Stoyanchev - * @since 3.1 - */ -public class FlashAttributesHandler { - - public static final String FLASH_ATTRIBUTES_SESSION_KEY = FlashAttributesHandler.class.getName() + ".attributes"; - - private final Set attributeNames = new HashSet(); - - private final Set> attributeTypes = new HashSet>(); - - /** - * TODO ... - */ - public FlashAttributesHandler(Class handlerType) { - FlashAttributes annotation = AnnotationUtils.findAnnotation(handlerType, FlashAttributes.class); - if (annotation != null) { - this.attributeNames.addAll(Arrays.asList(annotation.value())); - this.attributeTypes.addAll(Arrays.>asList(annotation.types())); - } - } - - /** - * Whether the controller represented by this handler has declared flash - * attribute names or types via @{@link FlashAttributes}. - */ - public boolean hasFlashAttributes() { - return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0)); - } - - /** - * TODO ... - */ - public boolean isFlashAttribute(String attributeName, Class attributeType) { - return (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)); - } - - /** - * TODO ... - */ - public void storeAttributes(WebRequest request, Map attributes) { - Map filtered = filterAttributes(attributes); - if (!filtered.isEmpty()) { - request.setAttribute(FLASH_ATTRIBUTES_SESSION_KEY, filtered, WebRequest.SCOPE_SESSION); - } - } - - private Map filterAttributes(Map attributes) { - Map result = new LinkedHashMap(); - for (String name : attributes.keySet()) { - Object value = attributes.get(name); - Class type = (value != null) ? value.getClass() : null; - if (isFlashAttribute(name, type)) { - result.put(name, value); - } - } - return result; - } - - /** - * TODO ... - */ - @SuppressWarnings("unchecked") - public Map retrieveAttributes(WebRequest request) { - return (Map) request.getAttribute(FLASH_ATTRIBUTES_SESSION_KEY, WebRequest.SCOPE_SESSION); - } - - /** - * TODO ... - */ - public void cleanupAttributes(WebRequest request) { - request.removeAttribute(FLASH_ATTRIBUTES_SESSION_KEY, WebRequest.SCOPE_SESSION); - } - -} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java index a85592cc87d..b8b2f578af2 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/ModelFactory.java @@ -35,7 +35,6 @@ import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; -import org.springframework.web.bind.support.FlashStatus; import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -66,23 +65,18 @@ public final class ModelFactory { private final SessionAttributesHandler sessionAttributesHandler; - private final FlashAttributesHandler flashAttributesHandler; - /** * Create a ModelFactory instance with the provided {@link ModelAttribute} methods. * @param attributeMethods {@link ModelAttribute} methods to initialize model instances with * @param binderFactory used to add {@link BindingResult} attributes to the model * @param sessionAttributesHandler used to access handler-specific session attributes - * @param flashAttributesHandler used to access flash attributes */ public ModelFactory(List attributeMethods, WebDataBinderFactory binderFactory, - SessionAttributesHandler sessionAttributesHandler, - FlashAttributesHandler flashAttributesHandler) { + SessionAttributesHandler sessionAttributesHandler) { this.attributeMethods = (attributeMethods != null) ? attributeMethods : new ArrayList(); this.binderFactory = binderFactory; this.sessionAttributesHandler = sessionAttributesHandler; - this.flashAttributesHandler = flashAttributesHandler; } /** @@ -93,21 +87,17 @@ public final class ModelFactory { *

  • Check the session for any controller-specific attributes not yet "remembered". * * @param request the current request - * @param mavContainer contains the model to initialize + * @param mavContainer contains the model to initialize * @param handlerMethod the @{@link RequestMapping} method for which the model is initialized * @throws Exception may arise from the invocation of @{@link ModelAttribute} methods */ - public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) + public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) throws Exception { - + Map sessionAttrs = this.sessionAttributesHandler.retrieveAttributes(request); - mavContainer.addAllAttributes(sessionAttrs); + mavContainer.mergeAttributes(sessionAttrs); - Map flashAttrs = this.flashAttributesHandler.retrieveAttributes(request); - mavContainer.addAllAttributes(flashAttrs); - this.flashAttributesHandler.cleanupAttributes(request); - - invokeAttributeMethods(request, mavContainer); + invokeModelAttributeMethods(request, mavContainer); checkHandlerSessionAttributes(request, mavContainer, handlerMethod); } @@ -116,7 +106,7 @@ public final class ModelFactory { * Invoke model attribute methods to populate the model. * If two methods return the same attribute, the attribute from the first method is added. */ - private void invokeAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer) + private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { for (InvocableHandlerMethod attrMethod : this.attributeMethods) { @@ -128,18 +118,23 @@ public final class ModelFactory { Object returnValue = attrMethod.invokeForRequest(request, mavContainer); if (!attrMethod.isVoid()){ - String valueName = getNameForReturnValue(returnValue, attrMethod.getReturnType()); - mavContainer.mergeAttribute(valueName, returnValue); + String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType()); + if (!mavContainer.containsAttribute(returnValueName)) { + mavContainer.addAttribute(returnValueName, returnValue); + } } } } /** - * Checks if any @{@link ModelAttribute} handler method arguments declared as - * session attributes via @{@link SessionAttributes} but are not already in the - * model. If found add them to the model, raise an exception otherwise. + * Checks for @{@link ModelAttribute} arguments in the signature of the + * {@link RequestMapping} method that are declared as session attributes + * via @{@link SessionAttributes} but are not already in the model. + * Those attributes may have been outside of this controller. + * Try to locate the attributes in the session or raise an exception. * - * @throws HttpSessionRequiredException raised if a handler session attribute could is missing + * @throws HttpSessionRequiredException raised if an attribute declared + * as session attribute is missing. */ private void checkHandlerSessionAttributes(NativeWebRequest request, ModelAndViewContainer mavContainer, @@ -203,11 +198,11 @@ public final class ModelFactory { * promotes model attributes to the session, and adds {@link BindingResult} attributes where missing. * @param request the current request * @param mavContainer the {@link ModelAndViewContainer} for the current request - * @param sessionStatus whether session processing is complete + * @param sessionStatus the session status for the current request * @throws Exception if the process of creating {@link BindingResult} attributes causes an error */ - public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer, - SessionStatus sessionStatus, FlashStatus flashStatus) throws Exception { + public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer, SessionStatus sessionStatus) + throws Exception { if (sessionStatus.isComplete()){ this.sessionAttributesHandler.cleanupAttributes(request); @@ -215,10 +210,6 @@ public final class ModelFactory { else { this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel()); } - - if (flashStatus.isActive()) { - this.flashAttributesHandler.storeAttributes(request, mavContainer.getModel()); - } if (mavContainer.isResolveView()) { updateBindingResult(request, mavContainer.getModel()); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java index 90d68fc04df..5e82f3e65c0 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java @@ -95,7 +95,7 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); Object target = (mavContainer.containsAttribute(name)) ? - mavContainer.getAttribute(name) : createAttribute(name, parameter, binderFactory, request); + mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request); WebDataBinder binder = binderFactory.createBinder(request, target, name); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java index ad4fa8cd511..ce0da85ed43 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelMethodProcessor.java @@ -56,9 +56,7 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand public boolean supportsReturnType(MethodParameter returnType) { Class paramType = returnType.getParameterType(); boolean hasModelAttr = returnType.getMethodAnnotation(ModelAttribute.class) != null; - - return (Model.class.isAssignableFrom(paramType) - || (Map.class.isAssignableFrom(paramType) && !hasModelAttr)); + return (Model.class.isAssignableFrom(paramType) || (Map.class.isAssignableFrom(paramType) && !hasModelAttr)); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java index 77673fb164a..999fb73d7c7 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java @@ -18,142 +18,141 @@ package org.springframework.web.method.support; import java.util.Map; +import org.springframework.ui.Model; import org.springframework.ui.ModelMap; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.validation.support.BindingAwareModelMap; /** - * Provides access to the model and a place to record model and view related decisions made by - * {@link HandlerMethodArgumentResolver}s or a {@link HandlerMethodReturnValueHandler}. - * - *

    In addition to storing model attributes and a view, the {@link ModelAndViewContainer} also provides - * a {@link #setResolveView(boolean)} flag, which can be used to request or bypass a view resolution phase. - * This is most commonly used from {@link HandlerMethodReturnValueHandler}s but in some cases may also be - * used from {@link HandlerMethodArgumentResolver}s such as when a handler method accepts an argument - * providing access to the response. When that is the case, if the handler method returns {@code null}, - * view resolution is skipped. + * Record model and view related decisions made by {@link HandlerMethodArgumentResolver}s + * and {@link HandlerMethodReturnValueHandler}s during the course of invocation of a + * request-handling method. * * @author Rossen Stoyanchev * @since 3.1 */ public class ModelAndViewContainer { - private String viewName; - private Object view; - private final ModelMap model; - private boolean resolveView = true; + private final ModelMap model = new BindingAwareModelMap(); + /** - * Create a {@link ModelAndViewContainer} instance with a {@link BindingAwareModelMap}. + * Create a new instance. */ public ModelAndViewContainer() { - this.model = new BindingAwareModelMap(); } /** - * Create a {@link ModelAndViewContainer} instance with the given {@link ModelMap} instance. - * @param model the model to use - */ - public ModelAndViewContainer(ModelMap model) { - Assert.notNull(model); - this.model = model; - } - - /** - * @return the model for the current request - */ - public ModelMap getModel() { - return model; - } - - /** - * @return the view name to use for view resolution, or {@code null} - */ - public String getViewName() { - return this.viewName; - } - - /** - * @param viewName the name of the view to use for view resolution + * Set a view name to be resolved by the DispatcherServlet via a ViewResolver. + * Will override any pre-existing view name or View. */ public void setViewName(String viewName) { - this.viewName = viewName; + this.view = viewName; } /** - * @return the view instance to use for view resolution + * Return the view name to be resolved by the DispatcherServlet via a + * ViewResolver, or {@code null} if a View object is set. */ - public Object getView() { - return this.view; + public String getViewName() { + return (this.view instanceof String ? (String) this.view : null); } /** - * @param view the view instance to use for view resolution + * Set a View object to be used by the DispatcherServlet. + * Will override any pre-existing view name or View. */ public void setView(Object view) { this.view = view; } - + /** - * @return whether the view resolution is requested ({@code true}), or should be bypassed ({@code false}) + * Return the View object, or {@code null} if we using a view name + * to be resolved by the DispatcherServlet via a ViewResolver. */ - public boolean isResolveView() { - return resolveView; + public Object getView() { + return this.view; } /** - * @param resolveView whether the view resolution is requested ({@code true}), or should be bypassed ({@code false}) + * Whether the view is a view reference specified via a name to be + * resolved by the DispatcherServlet via a ViewResolver. + */ + public boolean isViewReference() { + return (this.view instanceof String); + } + + /** + * Whether view resolution is required or not. The default value is "true". + *

    When set to "false" by a {@link HandlerMethodReturnValueHandler}, the response + * is considered complete and view resolution is not be performed. + *

    When set to "false" by {@link HandlerMethodArgumentResolver}, the response is + * considered complete only in combination with the request mapping method + * returning {@code null} or void. */ public void setResolveView(boolean resolveView) { this.resolveView = resolveView; } + + /** + * Whether view resolution is required or not. + */ + public boolean isResolveView() { + return this.resolveView; + } /** - * Whether model contains an attribute of the given name. - * @param name the name of the model attribute - * @return {@code true} if the model contains an attribute by that name and the name is not an empty string + * Return the underlying {@code ModelMap} instance, never {@code null}. + */ + public ModelMap getModel() { + return this.model; + } + + /** + * Add the supplied attribute to the underlying model. + * @see ModelMap#addAttribute(String, Object) + */ + public ModelAndViewContainer addAttribute(String name, Object value) { + this.model.addAttribute(name, value); + return this; + } + + /** + * Add the supplied attribute to the underlying model. + * @see Model#addAttribute(Object) + */ + public ModelAndViewContainer addAttribute(Object value) { + this.model.addAttribute(value); + return this; + } + + /** + * Copy all attributes to the underlying model. + * @see ModelMap#addAllAttributes(Map) + */ + public ModelAndViewContainer addAllAttributes(Map attributes) { + this.model.addAllAttributes(attributes); + return this; + } + + /** + * Copy attributes in the supplied Map with existing objects of + * the same name taking precedence (i.e. not getting replaced). + * @see ModelMap#mergeAttributes(Map) + */ + public ModelAndViewContainer mergeAttributes(Map attributes) { + this.model.mergeAttributes(attributes); + return this; + } + + /** + * Whether the underlying model contains the given attribute name. + * @see ModelMap#containsAttribute(String) */ public boolean containsAttribute(String name) { - return (StringUtils.hasText(name) && model.containsAttribute(name)); - } - - /** - * @param name the attribute to get from the model - * @return the attribute or {@code null} - */ - public Object getAttribute(String name) { - return model.get(name); - } - - /** - * Add the supplied attribute under the given name. - * @param name the name of the model attribute (never null) - * @param value the model attribute value (can be null) - */ - public void addAttribute(String name, Object value) { - model.addAttribute(name, value); - } - - /** - * Copy all attributes in the supplied Map into the model - */ - public void addAllAttributes(Map attributes) { - model.addAllAttributes(attributes); + return this.model.containsAttribute(name); } - /** - * Add the given attribute if the model does not already contain such an attribute. - * @param name the name of the attribute to check and add - * @param value the value of the attribute - */ - public void mergeAttribute(String name, Object value) { - if (!containsAttribute(name)) { - model.addAttribute(name, value); - } - } - -} \ No newline at end of file +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java index 351911a5195..df0eb30b8ec 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java @@ -41,7 +41,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.SessionAttributeStore; -import org.springframework.web.bind.support.SimpleFlashStatus; +import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.bind.support.SimpleSessionStatus; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -66,12 +66,8 @@ public class ModelFactoryTests { private SessionAttributesHandler sessionAttrsHandler; - private FlashAttributesHandler flashAttrsHandler; - private SessionAttributeStore sessionAttributeStore; - private ModelAndViewContainer mavContainer; - private NativeWebRequest webRequest; @Before @@ -82,42 +78,44 @@ public class ModelFactoryTests { handleSessionAttrMethod = new InvocableHandlerMethod(handler, method); sessionAttributeStore = new DefaultSessionAttributeStore(); sessionAttrsHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); - flashAttrsHandler = new FlashAttributesHandler(handlerType); - mavContainer = new ModelAndViewContainer(); webRequest = new ServletWebRequest(new MockHttpServletRequest()); } @Test public void modelAttributeMethod() throws Exception { ModelFactory modelFactory = createModelFactory("modelAttr", Model.class); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); modelFactory.initModel(webRequest, mavContainer, handleMethod); - assertEquals(Boolean.TRUE, mavContainer.getAttribute("modelAttr")); + assertEquals(Boolean.TRUE, mavContainer.getModel().get("modelAttr")); } @Test public void modelAttributeMethodWithSpecifiedName() throws Exception { ModelFactory modelFactory = createModelFactory("modelAttrWithName"); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); modelFactory.initModel(webRequest, mavContainer, handleMethod); - assertEquals(Boolean.TRUE, mavContainer.getAttribute("name")); + assertEquals(Boolean.TRUE, mavContainer.getModel().get("name")); } @Test public void modelAttributeMethodWithNameByConvention() throws Exception { ModelFactory modelFactory = createModelFactory("modelAttrConvention"); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); modelFactory.initModel(webRequest, mavContainer, handleMethod); - assertEquals(Boolean.TRUE, mavContainer.getAttribute("boolean")); + assertEquals(Boolean.TRUE, mavContainer.getModel().get("boolean")); } @Test public void modelAttributeMethodWithNullReturnValue() throws Exception { ModelFactory modelFactory = createModelFactory("nullModelAttr"); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); modelFactory.initModel(webRequest, mavContainer, handleMethod); assertTrue(mavContainer.containsAttribute("name")); - assertNull(mavContainer.getAttribute("name")); + assertNull(mavContainer.getModel().get("name")); } @Test @@ -128,30 +126,33 @@ public class ModelFactoryTests { assertTrue(sessionAttrsHandler.isHandlerSessionAttribute("sessionAttr", null)); ModelFactory modelFactory = createModelFactory("modelAttr", Model.class); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); modelFactory.initModel(webRequest, mavContainer, handleMethod); - assertEquals("sessionAttrValue", mavContainer.getAttribute("sessionAttr")); + assertEquals("sessionAttrValue", mavContainer.getModel().get("sessionAttr")); } @Test public void requiredSessionAttribute() throws Exception { - ModelFactory modelFactory = new ModelFactory(null, null, sessionAttrsHandler, flashAttrsHandler); + ModelFactory modelFactory = new ModelFactory(null, null, sessionAttrsHandler); try { - modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod); + modelFactory.initModel(webRequest, new ModelAndViewContainer(), handleSessionAttrMethod); fail("Expected HttpSessionRequiredException"); } catch (HttpSessionRequiredException e) { } sessionAttributeStore.storeAttribute(webRequest, "sessionAttr", "sessionAttrValue"); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); modelFactory.initModel(webRequest, mavContainer, handleSessionAttrMethod); - assertEquals("sessionAttrValue", mavContainer.getAttribute("sessionAttr")); + assertEquals("sessionAttrValue", mavContainer.getModel().get("sessionAttr")); } @Test public void updateModelBindingResultKeys() throws Exception { String attrName = "attr1"; Object attrValue = new Object(); + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAttribute(attrName, attrValue); WebDataBinder dataBinder = new WebDataBinder(attrValue, attrName); @@ -159,8 +160,8 @@ public class ModelFactoryTests { expect(binderFactory.createBinder(webRequest, attrValue, attrName)).andReturn(dataBinder); replay(binderFactory); - ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler, flashAttrsHandler); - modelFactory.updateModel(webRequest, mavContainer, new SimpleSessionStatus(), new SimpleFlashStatus()); + ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); + modelFactory.updateModel(webRequest, mavContainer, new SimpleSessionStatus()); assertEquals(attrValue, mavContainer.getModel().remove(attrName)); assertSame(dataBinder.getBindingResult(), mavContainer.getModel().remove(bindingResultKey(attrName))); @@ -174,6 +175,7 @@ public class ModelFactoryTests { String attrName = "sessionAttr"; String attrValue = "sessionAttrValue"; + ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAttribute(attrName, attrValue); sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue); @@ -185,15 +187,13 @@ public class ModelFactoryTests { expect(binderFactory.createBinder(webRequest, attrValue, attrName)).andReturn(dataBinder); replay(binderFactory); - SimpleSessionStatus status = new SimpleSessionStatus(); - status.setComplete(); + SessionStatus sessionStatus = new SimpleSessionStatus(); + sessionStatus.setComplete(); - // TODO: test with active FlashStatus + ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler); + modelFactory.updateModel(webRequest, mavContainer, sessionStatus); - ModelFactory modelFactory = new ModelFactory(null, binderFactory, sessionAttrsHandler, flashAttrsHandler); - modelFactory.updateModel(webRequest, mavContainer, status, new SimpleFlashStatus()); - - assertEquals(attrValue, mavContainer.getAttribute(attrName)); + assertEquals(attrValue, mavContainer.getModel().get(attrName)); assertNull(sessionAttributeStore.retrieveAttribute(webRequest, attrName)); verify(binderFactory); @@ -214,7 +214,7 @@ public class ModelFactoryTests { handlerMethod.setDataBinderFactory(null); handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); - return new ModelFactory(Arrays.asList(handlerMethod), null, sessionAttrsHandler, flashAttrsHandler); + return new ModelFactory(Arrays.asList(handlerMethod), null, sessionAttrsHandler); } @SessionAttributes("sessionAttr") @SuppressWarnings("unused")