1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.web.method.annotation;
18
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29
30 import org.springframework.beans.BeanUtils;
31 import org.springframework.core.Conventions;
32 import org.springframework.core.GenericTypeResolver;
33 import org.springframework.core.MethodParameter;
34 import org.springframework.ui.Model;
35 import org.springframework.ui.ModelMap;
36 import org.springframework.util.StringUtils;
37 import org.springframework.validation.BindingResult;
38 import org.springframework.web.HttpSessionRequiredException;
39 import org.springframework.web.bind.WebDataBinder;
40 import org.springframework.web.bind.annotation.ModelAttribute;
41 import org.springframework.web.bind.support.WebDataBinderFactory;
42 import org.springframework.web.context.request.NativeWebRequest;
43 import org.springframework.web.method.HandlerMethod;
44 import org.springframework.web.method.support.InvocableHandlerMethod;
45 import org.springframework.web.method.support.ModelAndViewContainer;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public final class ModelFactory {
61
62 private static final Log logger = LogFactory.getLog(ModelFactory.class);
63
64 private final List<ModelMethod> modelMethods = new ArrayList<ModelMethod>();
65
66 private final WebDataBinderFactory dataBinderFactory;
67
68 private final SessionAttributesHandler sessionAttributesHandler;
69
70
71
72
73
74
75
76
77 public ModelFactory(List<InvocableHandlerMethod> invocableMethods, WebDataBinderFactory dataBinderFactory,
78 SessionAttributesHandler sessionAttributesHandler) {
79
80 if (invocableMethods != null) {
81 for (InvocableHandlerMethod method : invocableMethods) {
82 this.modelMethods.add(new ModelMethod(method));
83 }
84 }
85 this.dataBinderFactory = dataBinderFactory;
86 this.sessionAttributesHandler = sessionAttributesHandler;
87 }
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 public void initModel(NativeWebRequest request, ModelAndViewContainer mavContainer, HandlerMethod handlerMethod)
104 throws Exception {
105
106 Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
107 mavContainer.mergeAttributes(sessionAttributes);
108
109 invokeModelAttributeMethods(request, mavContainer);
110
111 for (String name : findSessionAttributeArguments(handlerMethod)) {
112 if (!mavContainer.containsAttribute(name)) {
113 Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
114 if (value == null) {
115 throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
116 }
117 mavContainer.addAttribute(name, value);
118 }
119 }
120 }
121
122
123
124
125
126 private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer mavContainer)
127 throws Exception {
128
129 while (!this.modelMethods.isEmpty()) {
130 InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod();
131 String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
132 if (mavContainer.containsAttribute(modelName)) {
133 continue;
134 }
135
136 Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
137
138 if (!attrMethod.isVoid()){
139 String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
140 if (!mavContainer.containsAttribute(returnValueName)) {
141 mavContainer.addAttribute(returnValueName, returnValue);
142 }
143 }
144 }
145 }
146
147 private ModelMethod getNextModelMethod(ModelAndViewContainer mavContainer) {
148 for (ModelMethod modelMethod : this.modelMethods) {
149 if (modelMethod.checkDependencies(mavContainer)) {
150 if (logger.isTraceEnabled()) {
151 logger.trace("Selected @ModelAttribute method " + modelMethod);
152 }
153 this.modelMethods.remove(modelMethod);
154 return modelMethod;
155 }
156 }
157 ModelMethod modelMethod = this.modelMethods.get(0);
158 if (logger.isTraceEnabled()) {
159 logger.trace("Selected @ModelAttribute method (not present: " +
160 modelMethod.getUnresolvedDependencies(mavContainer)+ ") " + modelMethod);
161 }
162 this.modelMethods.remove(modelMethod);
163 return modelMethod;
164 }
165
166
167
168
169 private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) {
170 List<String> result = new ArrayList<String>();
171 for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
172 if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
173 String name = getNameForParameter(parameter);
174 if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) {
175 result.add(name);
176 }
177 }
178 }
179 return result;
180 }
181
182
183
184
185
186
187
188
189
190 public static String getNameForParameter(MethodParameter parameter) {
191 ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
192 String attrName = (annot != null) ? annot.value() : null;
193 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207 public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {
208 ModelAttribute annotation = returnType.getMethodAnnotation(ModelAttribute.class);
209 if (annotation != null && StringUtils.hasText(annotation.value())) {
210 return annotation.value();
211 }
212 else {
213 Method method = returnType.getMethod();
214 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getContainingClass());
215 return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
216 }
217 }
218
219
220
221
222
223
224
225
226 public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
227 ModelMap defaultModel = mavContainer.getDefaultModel();
228 if (mavContainer.getSessionStatus().isComplete()){
229 this.sessionAttributesHandler.cleanupAttributes(request);
230 }
231 else {
232 this.sessionAttributesHandler.storeAttributes(request, defaultModel);
233 }
234 if (!mavContainer.isRequestHandled() && mavContainer.getModel() == defaultModel) {
235 updateBindingResult(request, defaultModel);
236 }
237 }
238
239
240
241
242 private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
243 List<String> keyNames = new ArrayList<String>(model.keySet());
244 for (String name : keyNames) {
245 Object value = model.get(name);
246
247 if (isBindingCandidate(name, value)) {
248 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
249
250 if (!model.containsAttribute(bindingResultKey)) {
251 WebDataBinder dataBinder = dataBinderFactory.createBinder(request, value, name);
252 model.put(bindingResultKey, dataBinder.getBindingResult());
253 }
254 }
255 }
256 }
257
258
259
260
261 private boolean isBindingCandidate(String attributeName, Object value) {
262 if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
263 return false;
264 }
265
266 Class<?> attrType = (value != null) ? value.getClass() : null;
267 if (this.sessionAttributesHandler.isHandlerSessionAttribute(attributeName, attrType)) {
268 return true;
269 }
270
271 return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
272 !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
273 }
274
275
276 private static class ModelMethod {
277
278 private final InvocableHandlerMethod handlerMethod;
279
280 private final Set<String> dependencies = new HashSet<String>();
281
282
283 private ModelMethod(InvocableHandlerMethod handlerMethod) {
284 this.handlerMethod = handlerMethod;
285 for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
286 if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
287 this.dependencies.add(getNameForParameter(parameter));
288 }
289 }
290 }
291
292 public InvocableHandlerMethod getHandlerMethod() {
293 return this.handlerMethod;
294 }
295
296 public boolean checkDependencies(ModelAndViewContainer mavContainer) {
297 for (String name : this.dependencies) {
298 if (!mavContainer.containsAttribute(name)) {
299 return false;
300 }
301 }
302 return true;
303 }
304
305 public List<String> getUnresolvedDependencies(ModelAndViewContainer mavContainer) {
306 List<String> result = new ArrayList<String>(this.dependencies.size());
307 for (String name : this.dependencies) {
308 if (!mavContainer.containsAttribute(name)) {
309 result.add(name);
310 }
311 }
312 return result;
313 }
314
315 @Override
316 public String toString() {
317 return this.handlerMethod.getMethod().toGenericString();
318 }
319 }
320
321 }