SPR-6464 Drop @FlashAttributes, add ResponseContext, ViewResponse, and RedirectResponse types for annotated controllers to use to prepare a redirect response with flash attributes; Add FlashMap and FlashMapManager and update DispatcherServlet to discover and invoke the FlashMapManager.
This commit is contained in:
parent
11597c906d
commit
1df0cd9f20
|
|
@ -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.
|
||||
|
|
@ -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<ViewResolver> 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.
|
||||
* <p>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 <code>null</code>.
|
||||
* <p>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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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:
|
||||
* <ul>
|
||||
* <li>Create a new FlashMap and make it available to the current request
|
||||
* under the request attribute {@link #CURRENT_FLASH_MAP_ATTRIBUTE}.
|
||||
* <li>Locate the FlashMap saved on the previous request and expose its
|
||||
* contents as attributes in the current request.
|
||||
* <li>Remove expired flash map instances.
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache =
|
||||
new ConcurrentHashMap<Class<?>, SessionAttributesHandler>();
|
||||
|
||||
private final Map<Class<?>, FlashAttributesHandler> flashAttributesHandlerCache =
|
||||
new ConcurrentHashMap<Class<?>, FlashAttributesHandler>();
|
||||
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers;
|
||||
|
||||
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
|
||||
|
||||
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
|
||||
|
||||
private final Map<Class<?>, Set<Method>> initBinderMethodCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
|
||||
private final Map<Class<?>, WebDataBinderFactory> dataBinderFactoryCache =
|
||||
new ConcurrentHashMap<Class<?>, WebDataBinderFactory>();
|
||||
|
||||
private final Map<Class<?>, Set<Method>> modelAttributeMethodCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
|
||||
private final Map<Class<?>, ModelFactory> modelFactoryCache = new ConcurrentHashMap<Class<?>, ModelFactory>();
|
||||
|
||||
/**
|
||||
* Create a {@link RequestMappingHandlerAdapter} instance.
|
||||
|
|
@ -463,7 +459,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
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,49 +483,46 @@ 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;
|
||||
private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
|
||||
Class<?> handlerType = handlerMethod.getBeanType();
|
||||
SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
|
||||
if (sessionAttrHandler == null) {
|
||||
synchronized(this.sessionAttributesHandlerCache) {
|
||||
sessionAttrsHandler = this.sessionAttributesHandlerCache.get(handlerType);
|
||||
if (sessionAttrsHandler == null) {
|
||||
sessionAttrsHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
|
||||
this.sessionAttributesHandlerCache.put(handlerType, sessionAttrsHandler);
|
||||
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,
|
||||
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;
|
||||
|
|
@ -537,68 +530,59 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
|||
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<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
|
||||
|
||||
Class<?> handlerType = handlerMethod.getBeanType();
|
||||
Set<Method> binderMethods = initBinderMethodCache.get(handlerType);
|
||||
if (binderMethods == null) {
|
||||
binderMethods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
|
||||
initBinderMethodCache.put(handlerType, binderMethods);
|
||||
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;
|
||||
}
|
||||
|
||||
for (Method method : binderMethods) {
|
||||
Object bean = handlerMethod.getBean();
|
||||
InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
|
||||
private ModelFactory getModelFactory(HandlerMethod handlerMethod) {
|
||||
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
|
||||
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
|
||||
Class<?> handlerType = handlerMethod.getBeanType();
|
||||
ModelFactory modelFactory = this.modelFactoryCache.get(handlerType);
|
||||
if (modelFactory == null) {
|
||||
List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
|
||||
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);
|
||||
}
|
||||
return modelFactory;
|
||||
}
|
||||
|
||||
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) {
|
||||
Class<?> handlerType = handlerMethod.getBeanType();
|
||||
WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType);
|
||||
if (binderFactory == null) {
|
||||
List<InvocableHandlerMethod> binderMethods = new ArrayList<InvocableHandlerMethod>();
|
||||
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);
|
||||
initBinderMethods.add(binderMethod);
|
||||
binderMethods.add(binderMethod);
|
||||
}
|
||||
|
||||
return new ServletRequestDataBinderFactory(initBinderMethods, this.webBindingInitializer);
|
||||
binderFactory = new ServletRequestDataBinderFactory(binderMethods, this.webBindingInitializer);
|
||||
this.dataBinderFactoryCache.put(handlerType, binderFactory);
|
||||
}
|
||||
|
||||
private ModelFactory createModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
|
||||
List<InvocableHandlerMethod> modelAttrMethods = new ArrayList<InvocableHandlerMethod>();
|
||||
|
||||
Class<?> handlerType = handlerMethod.getBeanType();
|
||||
Set<Method> attributeMethods = modelAttributeMethodCache.get(handlerType);
|
||||
if (attributeMethods == null) {
|
||||
attributeMethods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
|
||||
modelAttributeMethodCache.put(handlerType, attributeMethods);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p><strong>Note:</strong> 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}.
|
||||
* <p><strong>Note:</strong> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 .
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
* <p>The default setting is "true".
|
||||
* <p>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.
|
||||
* <p>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}
|
||||
*
|
||||
* <p>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<String, FlashMap> allFlashMaps = retrieveAllFlashMaps(request, false);
|
||||
if (allFlashMaps != null && !allFlashMaps.isEmpty()) {
|
||||
Iterator<FlashMap> 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<String, FlashMap> 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<String, FlashMap> retrieveAllFlashMaps(HttpServletRequest request, boolean allowCreate) {
|
||||
HttpSession session = request.getSession(allowCreate);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, FlashMap> result = (Map<String, FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE);
|
||||
if (result == null && allowCreate) {
|
||||
synchronized (DefaultFlashMapManager.class) {
|
||||
result = (Map<String, FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE);
|
||||
if (result == null) {
|
||||
result = new ConcurrentHashMap<String, FlashMap>(5);
|
||||
session.setAttribute(FLASH_MAPS_SESSION_ATTRIBUTE, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>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<String, FlashMap> allFlashMaps = retrieveAllFlashMaps(request, true);
|
||||
flashMap.startExpirationPeriod(this.flashTimeout);
|
||||
String key = this.useUniqueFlashKey ? flashMap.getKey() : "flashMap";
|
||||
allFlashMaps.put(key, flashMap);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Object> entry : queryProperties(model).entrySet()) {
|
||||
Object rawValue = entry.getValue();
|
||||
Iterator<Object> valueIter;
|
||||
|
|
|
|||
|
|
@ -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<String, ?> model = (Map<String, ?>) 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<String, Object> attrs = (Map<String, Object>) req.getSession().getAttribute(flashAttributesKey);
|
||||
return (attrs != null) ? attrs.get(key) : null;
|
||||
}
|
||||
|
||||
private WebApplicationContext initServletWithModelExposingViewResolver(Class<?>... controllerClasses)
|
||||
throws ServletException {
|
||||
|
||||
return initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
|
||||
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<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
request.setAttribute(REQUEST_ATTRIBITE_MODEL, model);
|
||||
}
|
||||
public String getContentType() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
@ -1453,6 +1456,45 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
|||
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
|
||||
|
|
@ -2762,6 +2804,32 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
|||
}
|
||||
}
|
||||
|
||||
@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:
|
||||
|
||||
// @Ignore("Controller interface => no method-level @RequestMapping annotation")
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class DefaultMethodReturnValueHandlerTests {
|
|||
public void setUp() {
|
||||
mavResolvers = new ArrayList<ModelAndViewResolver>();
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String, FlashMap> allFlashMaps = new HashMap<String, FlashMap>();
|
||||
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<String, FlashMap> allFlashMaps = new HashMap<String, FlashMap>();
|
||||
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<String, FlashMap> allFlashMaps = new HashMap<String, FlashMap>();
|
||||
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<String, FlashMap> allFlashMaps = (Map<String, FlashMap>) 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -91,6 +93,35 @@ public class RedirectViewTests {
|
|||
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<String, Object>(), 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<String, Object>(), request, response);
|
||||
assertEquals(303, response.getStatus());
|
||||
assertEquals("http://url.somewhere.com", response.getHeader("Location"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyMap() throws Exception {
|
||||
String url = "/myUrl";
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>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();
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String> attributeNames = new HashSet<String>();
|
||||
|
||||
private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
|
||||
|
||||
/**
|
||||
* 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.<Class<?>>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<String, ?> attributes) {
|
||||
Map<String, Object> filtered = filterAttributes(attributes);
|
||||
if (!filtered.isEmpty()) {
|
||||
request.setAttribute(FLASH_ATTRIBUTES_SESSION_KEY, filtered, WebRequest.SCOPE_SESSION);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> filterAttributes(Map<String, ?> attributes) {
|
||||
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||
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<String, Object> retrieveAttributes(WebRequest request) {
|
||||
return (Map<String, Object>) request.getAttribute(FLASH_ATTRIBUTES_SESSION_KEY, WebRequest.SCOPE_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO ...
|
||||
*/
|
||||
public void cleanupAttributes(WebRequest request) {
|
||||
request.removeAttribute(FLASH_ATTRIBUTES_SESSION_KEY, WebRequest.SCOPE_SESSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<InvocableHandlerMethod> attributeMethods,
|
||||
WebDataBinderFactory binderFactory,
|
||||
SessionAttributesHandler sessionAttributesHandler,
|
||||
FlashAttributesHandler flashAttributesHandler) {
|
||||
SessionAttributesHandler sessionAttributesHandler) {
|
||||
this.attributeMethods = (attributeMethods != null) ? attributeMethods : new ArrayList<InvocableHandlerMethod>();
|
||||
this.binderFactory = binderFactory;
|
||||
this.sessionAttributesHandler = sessionAttributesHandler;
|
||||
this.flashAttributesHandler = flashAttributesHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -101,13 +95,9 @@ public final class ModelFactory {
|
|||
throws Exception {
|
||||
|
||||
Map<String, ?> sessionAttrs = this.sessionAttributesHandler.retrieveAttributes(request);
|
||||
mavContainer.addAllAttributes(sessionAttrs);
|
||||
mavContainer.mergeAttributes(sessionAttrs);
|
||||
|
||||
Map<String, ?> 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);
|
||||
|
|
@ -216,10 +211,6 @@ public final class ModelFactory {
|
|||
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
|
||||
}
|
||||
|
||||
if (flashStatus.isActive()) {
|
||||
this.flashAttributesHandler.storeAttributes(request, mavContainer.getModel());
|
||||
}
|
||||
|
||||
if (mavContainer.isResolveView()) {
|
||||
updateBindingResult(request, mavContainer.getModel());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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" })
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
*
|
||||
* <p>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".
|
||||
* <p>When set to "false" by a {@link HandlerMethodReturnValueHandler}, the response
|
||||
* is considered complete and view resolution is not be performed.
|
||||
* <p>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 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
|
||||
* Whether view resolution is required or not.
|
||||
*/
|
||||
public boolean isResolveView() {
|
||||
return this.resolveView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String, ?> attributes) {
|
||||
this.model.addAllAttributes(attributes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy attributes in the supplied <code>Map</code> with existing objects of
|
||||
* the same name taking precedence (i.e. not getting replaced).
|
||||
* @see ModelMap#mergeAttributes(Map)
|
||||
*/
|
||||
public ModelAndViewContainer mergeAttributes(Map<String, ?> 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<String, ?> attributes) {
|
||||
model.addAllAttributes(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
return this.model.containsAttribute(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue