|
|
|
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
|
|
|
|
/* |
|
|
|
|
* Copyright 2002-2013 the original author or authors. |
|
|
|
|
* Copyright 2002-2014 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. |
|
|
|
@ -33,7 +33,6 @@ import org.springframework.validation.BindingResult;
@@ -33,7 +33,6 @@ import org.springframework.validation.BindingResult;
|
|
|
|
|
import org.springframework.web.HttpSessionRequiredException; |
|
|
|
|
import org.springframework.web.bind.WebDataBinder; |
|
|
|
|
import org.springframework.web.bind.annotation.ModelAttribute; |
|
|
|
|
import org.springframework.web.bind.annotation.SessionAttributes; |
|
|
|
|
import org.springframework.web.bind.support.WebDataBinderFactory; |
|
|
|
|
import org.springframework.web.context.request.NativeWebRequest; |
|
|
|
|
import org.springframework.web.method.HandlerMethod; |
|
|
|
@ -42,57 +41,59 @@ import org.springframework.web.method.support.ModelAndViewContainer;
@@ -42,57 +41,59 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Provides methods to initialize the {@link Model} before controller method |
|
|
|
|
* invocation and to update it afterwards. On initialization, the model is |
|
|
|
|
* populated with attributes from the session or by invoking |
|
|
|
|
* {@code @ModelAttribute} methods. On update, model attributes are |
|
|
|
|
* synchronized with the session -- either adding or removing them. |
|
|
|
|
* Also {@link BindingResult} attributes where missing. |
|
|
|
|
* invocation and to update it afterwards. |
|
|
|
|
* |
|
|
|
|
* <p>On initialization, the model is populated with attributes from the session |
|
|
|
|
* and by invoking methods annotated with {@code @ModelAttribute}. |
|
|
|
|
* |
|
|
|
|
* <p>On update, model attributes are synchronized with the session and also |
|
|
|
|
* {@link BindingResult} attributes are added where missing. |
|
|
|
|
* |
|
|
|
|
* @author Rossen Stoyanchev |
|
|
|
|
* @since 3.1 |
|
|
|
|
*/ |
|
|
|
|
public final class ModelFactory { |
|
|
|
|
|
|
|
|
|
private final List<InvocableHandlerMethod> attributeMethods; |
|
|
|
|
private final List<InvocableHandlerMethod> handlerMethods; |
|
|
|
|
|
|
|
|
|
private final WebDataBinderFactory binderFactory; |
|
|
|
|
private final WebDataBinderFactory dataBinderFactory; |
|
|
|
|
|
|
|
|
|
private final SessionAttributesHandler sessionAttributesHandler; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Create a new instance with the given {@code @ModelAttribute} methods. |
|
|
|
|
* @param attributeMethods for model initialization |
|
|
|
|
* @param binderFactory for adding {@link BindingResult} attributes |
|
|
|
|
* @param handlerMethods the {@code @ModelAttribute} methods to invoke |
|
|
|
|
* @param dataBinderFactory for preparation of {@link BindingResult} attributes |
|
|
|
|
* @param sessionAttributesHandler for access to session attributes |
|
|
|
|
*/ |
|
|
|
|
public ModelFactory(List<InvocableHandlerMethod> attributeMethods, |
|
|
|
|
WebDataBinderFactory binderFactory, |
|
|
|
|
SessionAttributesHandler sessionAttributesHandler) { |
|
|
|
|
this.attributeMethods = (attributeMethods != null) ? attributeMethods : new ArrayList<InvocableHandlerMethod>(); |
|
|
|
|
this.binderFactory = binderFactory; |
|
|
|
|
public ModelFactory(List<InvocableHandlerMethod> handlerMethods, WebDataBinderFactory dataBinderFactory, |
|
|
|
|
SessionAttributesHandler sessionAttributesHandler) { |
|
|
|
|
|
|
|
|
|
this.handlerMethods = (handlerMethods != null) ? handlerMethods : new ArrayList<InvocableHandlerMethod>(); |
|
|
|
|
this.dataBinderFactory = dataBinderFactory; |
|
|
|
|
this.sessionAttributesHandler = sessionAttributesHandler; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Populate the model in the following order: |
|
|
|
|
* <ol> |
|
|
|
|
* <li>Retrieve "known" session attributes -- i.e. attributes listed via |
|
|
|
|
* {@link SessionAttributes @SessionAttributes} and previously stored in |
|
|
|
|
* the in the model at least once |
|
|
|
|
* <li>Invoke {@link ModelAttribute @ModelAttribute} methods |
|
|
|
|
* <li>Find method arguments eligible as session attributes and retrieve |
|
|
|
|
* them if they're not already present in the model |
|
|
|
|
* <li>Retrieve "known" session attributes listed as {@code @SessionAttributes}. |
|
|
|
|
* <li>Invoke {@code @ModelAttribute} methods |
|
|
|
|
* <li>Find {@code @ModelAttribute} method arguments also listed as |
|
|
|
|
* {@code @SessionAttributes} and ensure they're present in the model raising |
|
|
|
|
* an exception if necessary. |
|
|
|
|
* </ol> |
|
|
|
|
* @param request the current request |
|
|
|
|
* @param mavContainer contains the model to be initialized |
|
|
|
|
* @param mavContainer a container with the model to be initialized |
|
|
|
|
* @param handlerMethod the method for which the model is initialized |
|
|
|
|
* @throws Exception may arise from {@code @ModelAttribute} methods |
|
|
|
|
*/ |
|
|
|
|
public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod) |
|
|
|
|
throws Exception { |
|
|
|
|
|
|
|
|
|
Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request); |
|
|
|
|
mavContainer.mergeAttributes(attributesInSession); |
|
|
|
|
Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); |
|
|
|
|
mavContainer.mergeAttributes(sessionAttributes); |
|
|
|
|
|
|
|
|
|
invokeModelAttributeMethods(request, mavContainer); |
|
|
|
|
|
|
|
|
@ -108,13 +109,13 @@ public final class ModelFactory {
@@ -108,13 +109,13 @@ public final class ModelFactory {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Invoke model attribute methods to populate the model. Attributes are |
|
|
|
|
* added only if not already present in the model. |
|
|
|
|
* Invoke model attribute methods to populate the model. |
|
|
|
|
* Attributes are added only if not already present in the model. |
|
|
|
|
*/ |
|
|
|
|
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer) |
|
|
|
|
throws Exception { |
|
|
|
|
|
|
|
|
|
for (InvocableHandlerMethod attrMethod : this.attributeMethods) { |
|
|
|
|
for (InvocableHandlerMethod attrMethod : this.handlerMethods) { |
|
|
|
|
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value(); |
|
|
|
|
if (mavContainer.containsAttribute(modelName)) { |
|
|
|
|
continue; |
|
|
|
@ -132,15 +133,14 @@ public final class ModelFactory {
@@ -132,15 +133,14 @@ public final class ModelFactory {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Return all {@code @ModelAttribute} arguments declared as session |
|
|
|
|
* attributes via {@code @SessionAttributes}. |
|
|
|
|
* Find {@code @ModelAttribute} arguments also listed as {@code @SessionAttributes}. |
|
|
|
|
*/ |
|
|
|
|
private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) { |
|
|
|
|
List<String> result = new ArrayList<String>(); |
|
|
|
|
for (MethodParameter param : handlerMethod.getMethodParameters()) { |
|
|
|
|
if (param.hasParameterAnnotation(ModelAttribute.class)) { |
|
|
|
|
String name = getNameForParameter(param); |
|
|
|
|
if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, param.getParameterType())) { |
|
|
|
|
for (MethodParameter parameter : handlerMethod.getMethodParameters()) { |
|
|
|
|
if (parameter.hasParameterAnnotation(ModelAttribute.class)) { |
|
|
|
|
String name = getNameForParameter(parameter); |
|
|
|
|
if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) { |
|
|
|
|
result.add(name); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -149,11 +149,24 @@ public final class ModelFactory {
@@ -149,11 +149,24 @@ public final class ModelFactory {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Derive the model attribute name for the given return value using |
|
|
|
|
* one of the following: |
|
|
|
|
* Derives the model attribute name for a method parameter based on: |
|
|
|
|
* <ol> |
|
|
|
|
* <li>The parameter {@code @ModelAttribute} annotation value |
|
|
|
|
* <li>The parameter type |
|
|
|
|
* </ol> |
|
|
|
|
* @return the derived name; never {@code null} or an empty string |
|
|
|
|
*/ |
|
|
|
|
public static String getNameForParameter(MethodParameter parameter) { |
|
|
|
|
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); |
|
|
|
|
String attrName = (annot != null) ? annot.value() : null; |
|
|
|
|
return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Derive the model attribute name for the given return value using one of: |
|
|
|
|
* <ol> |
|
|
|
|
* <li>The method {@code ModelAttribute} annotation value |
|
|
|
|
* <li>The declared return type if it is other than {@code Object} |
|
|
|
|
* <li>The declared return type if it is more specific than {@code Object} |
|
|
|
|
* <li>The actual return value type |
|
|
|
|
* </ol> |
|
|
|
|
* @param returnValue the value returned from a method invocation |
|
|
|
@ -161,9 +174,9 @@ public final class ModelFactory {
@@ -161,9 +174,9 @@ public final class ModelFactory {
|
|
|
|
|
* @return the model name, never {@code null} nor empty |
|
|
|
|
*/ |
|
|
|
|
public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) { |
|
|
|
|
ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class); |
|
|
|
|
if (annot != null && StringUtils.hasText(annot.value())) { |
|
|
|
|
return annot.value(); |
|
|
|
|
ModelAttribute annotation = returnType.getMethodAnnotation(ModelAttribute.class); |
|
|
|
|
if (annotation != null && StringUtils.hasText(annotation.value())) { |
|
|
|
|
return annotation.value(); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
Method method = returnType.getMethod(); |
|
|
|
@ -173,35 +186,19 @@ public final class ModelFactory {
@@ -173,35 +186,19 @@ public final class ModelFactory {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Derives the model attribute name for a method parameter based on: |
|
|
|
|
* <ol> |
|
|
|
|
* <li>The parameter {@code @ModelAttribute} annotation value |
|
|
|
|
* <li>The parameter type |
|
|
|
|
* </ol> |
|
|
|
|
* @return the derived name; never {@code null} or an empty string |
|
|
|
|
*/ |
|
|
|
|
public static String getNameForParameter(MethodParameter parameter) { |
|
|
|
|
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); |
|
|
|
|
String attrName = (annot != null) ? annot.value() : null; |
|
|
|
|
return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Synchronize model attributes with the session. Add {@link BindingResult} |
|
|
|
|
* attributes where necessary. |
|
|
|
|
* Promote model attributes listed as {@code @SessionAttributes} to the session. |
|
|
|
|
* Add {@link BindingResult} attributes where necessary. |
|
|
|
|
* @param request the current request |
|
|
|
|
* @param mavContainer contains the model to update |
|
|
|
|
* @throws Exception if creating BindingResult attributes fails |
|
|
|
|
*/ |
|
|
|
|
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception { |
|
|
|
|
|
|
|
|
|
if (mavContainer.getSessionStatus().isComplete()){ |
|
|
|
|
this.sessionAttributesHandler.cleanupAttributes(request); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!mavContainer.isRequestHandled()) { |
|
|
|
|
updateBindingResult(request, mavContainer.getModel()); |
|
|
|
|
} |
|
|
|
@ -219,7 +216,7 @@ public final class ModelFactory {
@@ -219,7 +216,7 @@ public final class ModelFactory {
|
|
|
|
|
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name; |
|
|
|
|
|
|
|
|
|
if (!model.containsAttribute(bindingResultKey)) { |
|
|
|
|
WebDataBinder dataBinder = binderFactory.createBinder(request, value, name); |
|
|
|
|
WebDataBinder dataBinder = dataBinderFactory.createBinder(request, value, name); |
|
|
|
|
model.put(bindingResultKey, dataBinder.getBindingResult()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|