001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.bind.annotation.support; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Array; 021import java.lang.reflect.GenericArrayType; 022import java.lang.reflect.InvocationTargetException; 023import java.lang.reflect.Method; 024import java.lang.reflect.ParameterizedType; 025import java.lang.reflect.Type; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Iterator; 030import java.util.LinkedHashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037 038import org.springframework.beans.BeanUtils; 039import org.springframework.beans.factory.annotation.Value; 040import org.springframework.core.BridgeMethodResolver; 041import org.springframework.core.Conventions; 042import org.springframework.core.GenericTypeResolver; 043import org.springframework.core.MethodParameter; 044import org.springframework.core.ParameterNameDiscoverer; 045import org.springframework.core.annotation.AnnotationUtils; 046import org.springframework.core.annotation.SynthesizingMethodParameter; 047import org.springframework.http.HttpEntity; 048import org.springframework.http.HttpHeaders; 049import org.springframework.http.HttpInputMessage; 050import org.springframework.http.HttpOutputMessage; 051import org.springframework.http.MediaType; 052import org.springframework.http.converter.HttpMessageConverter; 053import org.springframework.ui.ExtendedModelMap; 054import org.springframework.ui.Model; 055import org.springframework.util.Assert; 056import org.springframework.util.ClassUtils; 057import org.springframework.util.LinkedMultiValueMap; 058import org.springframework.util.MultiValueMap; 059import org.springframework.util.ReflectionUtils; 060import org.springframework.util.StringUtils; 061import org.springframework.validation.BindException; 062import org.springframework.validation.BindingResult; 063import org.springframework.validation.Errors; 064import org.springframework.validation.annotation.Validated; 065import org.springframework.web.HttpMediaTypeNotSupportedException; 066import org.springframework.web.bind.WebDataBinder; 067import org.springframework.web.bind.annotation.CookieValue; 068import org.springframework.web.bind.annotation.InitBinder; 069import org.springframework.web.bind.annotation.ModelAttribute; 070import org.springframework.web.bind.annotation.PathVariable; 071import org.springframework.web.bind.annotation.RequestBody; 072import org.springframework.web.bind.annotation.RequestHeader; 073import org.springframework.web.bind.annotation.RequestParam; 074import org.springframework.web.bind.annotation.ValueConstants; 075import org.springframework.web.bind.support.DefaultSessionAttributeStore; 076import org.springframework.web.bind.support.SessionAttributeStore; 077import org.springframework.web.bind.support.SessionStatus; 078import org.springframework.web.bind.support.SimpleSessionStatus; 079import org.springframework.web.bind.support.WebArgumentResolver; 080import org.springframework.web.bind.support.WebBindingInitializer; 081import org.springframework.web.bind.support.WebRequestDataBinder; 082import org.springframework.web.context.request.NativeWebRequest; 083import org.springframework.web.context.request.WebRequest; 084import org.springframework.web.multipart.MultipartFile; 085import org.springframework.web.multipart.MultipartRequest; 086 087/** 088 * Support class for invoking an annotated handler method. Operates on the introspection 089 * results of a {@link HandlerMethodResolver} for a specific handler type. 090 * 091 * <p>Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} 092 * and {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}. 093 * 094 * @author Juergen Hoeller 095 * @author Arjen Poutsma 096 * @since 2.5.2 097 * @see #invokeHandlerMethod 098 * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure 099 */ 100@Deprecated 101public class HandlerMethodInvoker { 102 103 private static final String MODEL_KEY_PREFIX_STALE = SessionAttributeStore.class.getName() + ".STALE."; 104 105 /** We'll create a lot of these objects, so we don't want a new logger every time. */ 106 private static final Log logger = LogFactory.getLog(HandlerMethodInvoker.class); 107 108 private final HandlerMethodResolver methodResolver; 109 110 private final WebBindingInitializer bindingInitializer; 111 112 private final SessionAttributeStore sessionAttributeStore; 113 114 private final ParameterNameDiscoverer parameterNameDiscoverer; 115 116 private final WebArgumentResolver[] customArgumentResolvers; 117 118 private final HttpMessageConverter<?>[] messageConverters; 119 120 private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); 121 122 123 public HandlerMethodInvoker(HandlerMethodResolver methodResolver) { 124 this(methodResolver, null); 125 } 126 127 public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) { 128 this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, null, null); 129 } 130 131 public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer, 132 SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer, 133 WebArgumentResolver[] customArgumentResolvers, HttpMessageConverter<?>[] messageConverters) { 134 135 this.methodResolver = methodResolver; 136 this.bindingInitializer = bindingInitializer; 137 this.sessionAttributeStore = sessionAttributeStore; 138 this.parameterNameDiscoverer = parameterNameDiscoverer; 139 this.customArgumentResolvers = customArgumentResolvers; 140 this.messageConverters = messageConverters; 141 } 142 143 144 public final Object invokeHandlerMethod(Method handlerMethod, Object handler, 145 NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { 146 147 Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod); 148 try { 149 boolean debug = logger.isDebugEnabled(); 150 for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { 151 Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName); 152 if (attrValue != null) { 153 implicitModel.addAttribute(attrName, attrValue); 154 } 155 } 156 for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) { 157 Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod); 158 Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel); 159 if (debug) { 160 logger.debug("Invoking model attribute method: " + attributeMethodToInvoke); 161 } 162 String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value(); 163 if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) { 164 continue; 165 } 166 ReflectionUtils.makeAccessible(attributeMethodToInvoke); 167 Object attrValue = attributeMethodToInvoke.invoke(handler, args); 168 if ("".equals(attrName)) { 169 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass()); 170 attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue); 171 } 172 if (!implicitModel.containsAttribute(attrName)) { 173 implicitModel.addAttribute(attrName, attrValue); 174 } 175 } 176 Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); 177 if (debug) { 178 logger.debug("Invoking request handler method: " + handlerMethodToInvoke); 179 } 180 ReflectionUtils.makeAccessible(handlerMethodToInvoke); 181 return handlerMethodToInvoke.invoke(handler, args); 182 } 183 catch (IllegalStateException ex) { 184 // Internal assertion failed (e.g. invalid signature): 185 // throw exception with full handler method context... 186 throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex); 187 } 188 catch (InvocationTargetException ex) { 189 // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception... 190 ReflectionUtils.rethrowException(ex.getTargetException()); 191 return null; 192 } 193 } 194 195 public final void updateModelAttributes(Object handler, Map<String, Object> mavModel, 196 ExtendedModelMap implicitModel, NativeWebRequest webRequest) throws Exception { 197 198 if (this.methodResolver.hasSessionAttributes() && this.sessionStatus.isComplete()) { 199 for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { 200 this.sessionAttributeStore.cleanupAttribute(webRequest, attrName); 201 } 202 } 203 204 // Expose model attributes as session attributes, if required. 205 // Expose BindingResults for all attributes, making custom editors available. 206 Map<String, Object> model = (mavModel != null ? mavModel : implicitModel); 207 if (model != null) { 208 try { 209 String[] originalAttrNames = StringUtils.toStringArray(model.keySet()); 210 for (String attrName : originalAttrNames) { 211 Object attrValue = model.get(attrName); 212 boolean isSessionAttr = this.methodResolver.isSessionAttribute( 213 attrName, (attrValue != null ? attrValue.getClass() : null)); 214 if (isSessionAttr) { 215 if (this.sessionStatus.isComplete()) { 216 implicitModel.put(MODEL_KEY_PREFIX_STALE + attrName, Boolean.TRUE); 217 } 218 else if (!implicitModel.containsKey(MODEL_KEY_PREFIX_STALE + attrName)) { 219 this.sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue); 220 } 221 } 222 if (!attrName.startsWith(BindingResult.MODEL_KEY_PREFIX) && 223 (isSessionAttr || isBindingCandidate(attrValue))) { 224 String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attrName; 225 if (mavModel != null && !model.containsKey(bindingResultKey)) { 226 WebDataBinder binder = createBinder(webRequest, attrValue, attrName); 227 initBinder(handler, attrName, binder, webRequest); 228 mavModel.put(bindingResultKey, binder.getBindingResult()); 229 } 230 } 231 } 232 } 233 catch (InvocationTargetException ex) { 234 // User-defined @InitBinder method threw an exception... 235 ReflectionUtils.rethrowException(ex.getTargetException()); 236 } 237 } 238 } 239 240 241 private Object[] resolveHandlerArguments(Method handlerMethod, Object handler, 242 NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { 243 244 Class<?>[] paramTypes = handlerMethod.getParameterTypes(); 245 Object[] args = new Object[paramTypes.length]; 246 247 for (int i = 0; i < args.length; i++) { 248 MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i); 249 methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); 250 GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); 251 String paramName = null; 252 String headerName = null; 253 boolean requestBodyFound = false; 254 String cookieName = null; 255 String pathVarName = null; 256 String attrName = null; 257 boolean required = false; 258 String defaultValue = null; 259 boolean validate = false; 260 Object[] validationHints = null; 261 int annotationsFound = 0; 262 Annotation[] paramAnns = methodParam.getParameterAnnotations(); 263 264 for (Annotation paramAnn : paramAnns) { 265 if (RequestParam.class.isInstance(paramAnn)) { 266 RequestParam requestParam = (RequestParam) paramAnn; 267 paramName = requestParam.name(); 268 required = requestParam.required(); 269 defaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); 270 annotationsFound++; 271 } 272 else if (RequestHeader.class.isInstance(paramAnn)) { 273 RequestHeader requestHeader = (RequestHeader) paramAnn; 274 headerName = requestHeader.name(); 275 required = requestHeader.required(); 276 defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue()); 277 annotationsFound++; 278 } 279 else if (RequestBody.class.isInstance(paramAnn)) { 280 requestBodyFound = true; 281 annotationsFound++; 282 } 283 else if (CookieValue.class.isInstance(paramAnn)) { 284 CookieValue cookieValue = (CookieValue) paramAnn; 285 cookieName = cookieValue.name(); 286 required = cookieValue.required(); 287 defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue()); 288 annotationsFound++; 289 } 290 else if (PathVariable.class.isInstance(paramAnn)) { 291 PathVariable pathVar = (PathVariable) paramAnn; 292 pathVarName = pathVar.value(); 293 annotationsFound++; 294 } 295 else if (ModelAttribute.class.isInstance(paramAnn)) { 296 ModelAttribute attr = (ModelAttribute) paramAnn; 297 attrName = attr.value(); 298 annotationsFound++; 299 } 300 else if (Value.class.isInstance(paramAnn)) { 301 defaultValue = ((Value) paramAnn).value(); 302 } 303 else { 304 Validated validatedAnn = AnnotationUtils.getAnnotation(paramAnn, Validated.class); 305 if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) { 306 validate = true; 307 Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn)); 308 validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints}); 309 } 310 } 311 } 312 313 if (annotationsFound > 1) { 314 throw new IllegalStateException("Handler parameter annotations are exclusive choices - " + 315 "do not specify more than one such annotation on the same parameter: " + handlerMethod); 316 } 317 318 if (annotationsFound == 0) { 319 Object argValue = resolveCommonArgument(methodParam, webRequest); 320 if (argValue != WebArgumentResolver.UNRESOLVED) { 321 args[i] = argValue; 322 } 323 else if (defaultValue != null) { 324 args[i] = resolveDefaultValue(defaultValue); 325 } 326 else { 327 Class<?> paramType = methodParam.getParameterType(); 328 if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) { 329 if (!paramType.isAssignableFrom(implicitModel.getClass())) { 330 throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " + 331 "Model or Map but is not assignable from the actual model. You may need to switch " + 332 "newer MVC infrastructure classes to use this argument."); 333 } 334 args[i] = implicitModel; 335 } 336 else if (SessionStatus.class.isAssignableFrom(paramType)) { 337 args[i] = this.sessionStatus; 338 } 339 else if (HttpEntity.class.isAssignableFrom(paramType)) { 340 args[i] = resolveHttpEntityRequest(methodParam, webRequest); 341 } 342 else if (Errors.class.isAssignableFrom(paramType)) { 343 throw new IllegalStateException("Errors/BindingResult argument declared " + 344 "without preceding model attribute. Check your handler method signature!"); 345 } 346 else if (BeanUtils.isSimpleProperty(paramType)) { 347 paramName = ""; 348 } 349 else { 350 attrName = ""; 351 } 352 } 353 } 354 355 if (paramName != null) { 356 args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler); 357 } 358 else if (headerName != null) { 359 args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler); 360 } 361 else if (requestBodyFound) { 362 args[i] = resolveRequestBody(methodParam, webRequest, handler); 363 } 364 else if (cookieName != null) { 365 args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler); 366 } 367 else if (pathVarName != null) { 368 args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler); 369 } 370 else if (attrName != null) { 371 WebDataBinder binder = 372 resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler); 373 boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1])); 374 if (binder.getTarget() != null) { 375 doBind(binder, webRequest, validate, validationHints, !assignBindingResult); 376 } 377 args[i] = binder.getTarget(); 378 if (assignBindingResult) { 379 args[i + 1] = binder.getBindingResult(); 380 i++; 381 } 382 implicitModel.putAll(binder.getBindingResult().getModel()); 383 } 384 } 385 386 return args; 387 } 388 389 protected void initBinder(Object handler, String attrName, WebDataBinder binder, NativeWebRequest webRequest) 390 throws Exception { 391 392 if (this.bindingInitializer != null) { 393 this.bindingInitializer.initBinder(binder, webRequest); 394 } 395 if (handler != null) { 396 Set<Method> initBinderMethods = this.methodResolver.getInitBinderMethods(); 397 if (!initBinderMethods.isEmpty()) { 398 boolean debug = logger.isDebugEnabled(); 399 for (Method initBinderMethod : initBinderMethods) { 400 Method methodToInvoke = BridgeMethodResolver.findBridgedMethod(initBinderMethod); 401 String[] targetNames = AnnotationUtils.findAnnotation(initBinderMethod, InitBinder.class).value(); 402 if (targetNames.length == 0 || Arrays.asList(targetNames).contains(attrName)) { 403 Object[] initBinderArgs = 404 resolveInitBinderArguments(handler, methodToInvoke, binder, webRequest); 405 if (debug) { 406 logger.debug("Invoking init-binder method: " + methodToInvoke); 407 } 408 ReflectionUtils.makeAccessible(methodToInvoke); 409 Object returnValue = methodToInvoke.invoke(handler, initBinderArgs); 410 if (returnValue != null) { 411 throw new IllegalStateException( 412 "InitBinder methods must not have a return value: " + methodToInvoke); 413 } 414 } 415 } 416 } 417 } 418 } 419 420 private Object[] resolveInitBinderArguments(Object handler, Method initBinderMethod, 421 WebDataBinder binder, NativeWebRequest webRequest) throws Exception { 422 423 Class<?>[] initBinderParams = initBinderMethod.getParameterTypes(); 424 Object[] initBinderArgs = new Object[initBinderParams.length]; 425 426 for (int i = 0; i < initBinderArgs.length; i++) { 427 MethodParameter methodParam = new SynthesizingMethodParameter(initBinderMethod, i); 428 methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); 429 GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); 430 String paramName = null; 431 boolean paramRequired = false; 432 String paramDefaultValue = null; 433 String pathVarName = null; 434 Annotation[] paramAnns = methodParam.getParameterAnnotations(); 435 436 for (Annotation paramAnn : paramAnns) { 437 if (RequestParam.class.isInstance(paramAnn)) { 438 RequestParam requestParam = (RequestParam) paramAnn; 439 paramName = requestParam.name(); 440 paramRequired = requestParam.required(); 441 paramDefaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); 442 break; 443 } 444 else if (ModelAttribute.class.isInstance(paramAnn)) { 445 throw new IllegalStateException( 446 "@ModelAttribute is not supported on @InitBinder methods: " + initBinderMethod); 447 } 448 else if (PathVariable.class.isInstance(paramAnn)) { 449 PathVariable pathVar = (PathVariable) paramAnn; 450 pathVarName = pathVar.value(); 451 } 452 } 453 454 if (paramName == null && pathVarName == null) { 455 Object argValue = resolveCommonArgument(methodParam, webRequest); 456 if (argValue != WebArgumentResolver.UNRESOLVED) { 457 initBinderArgs[i] = argValue; 458 } 459 else { 460 Class<?> paramType = initBinderParams[i]; 461 if (paramType.isInstance(binder)) { 462 initBinderArgs[i] = binder; 463 } 464 else if (BeanUtils.isSimpleProperty(paramType)) { 465 paramName = ""; 466 } 467 else { 468 throw new IllegalStateException("Unsupported argument [" + paramType.getName() + 469 "] for @InitBinder method: " + initBinderMethod); 470 } 471 } 472 } 473 474 if (paramName != null) { 475 initBinderArgs[i] = 476 resolveRequestParam(paramName, paramRequired, paramDefaultValue, methodParam, webRequest, null); 477 } 478 else if (pathVarName != null) { 479 initBinderArgs[i] = resolvePathVariable(pathVarName, methodParam, webRequest, null); 480 } 481 } 482 483 return initBinderArgs; 484 } 485 486 @SuppressWarnings("unchecked") 487 private Object resolveRequestParam(String paramName, boolean required, String defaultValue, 488 MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) 489 throws Exception { 490 491 Class<?> paramType = methodParam.getParameterType(); 492 if (Map.class.isAssignableFrom(paramType) && paramName.length() == 0) { 493 return resolveRequestParamMap((Class<? extends Map<?, ?>>) paramType, webRequest); 494 } 495 if (paramName.length() == 0) { 496 paramName = getRequiredParameterName(methodParam); 497 } 498 Object paramValue = null; 499 MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class); 500 if (multipartRequest != null) { 501 List<MultipartFile> files = multipartRequest.getFiles(paramName); 502 if (!files.isEmpty()) { 503 paramValue = (files.size() == 1 ? files.get(0) : files); 504 } 505 } 506 if (paramValue == null) { 507 String[] paramValues = webRequest.getParameterValues(paramName); 508 if (paramValues != null) { 509 paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues); 510 } 511 } 512 if (paramValue == null) { 513 if (defaultValue != null) { 514 paramValue = resolveDefaultValue(defaultValue); 515 } 516 else if (required) { 517 raiseMissingParameterException(paramName, paramType); 518 } 519 paramValue = checkValue(paramName, paramValue, paramType); 520 } 521 WebDataBinder binder = createBinder(webRequest, null, paramName); 522 initBinder(handlerForInitBinderCall, paramName, binder, webRequest); 523 return binder.convertIfNecessary(paramValue, paramType, methodParam); 524 } 525 526 private Map<String, ?> resolveRequestParamMap(Class<? extends Map<?, ?>> mapType, NativeWebRequest webRequest) { 527 Map<String, String[]> parameterMap = webRequest.getParameterMap(); 528 if (MultiValueMap.class.isAssignableFrom(mapType)) { 529 MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(parameterMap.size()); 530 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { 531 for (String value : entry.getValue()) { 532 result.add(entry.getKey(), value); 533 } 534 } 535 return result; 536 } 537 else { 538 Map<String, String> result = new LinkedHashMap<String, String>(parameterMap.size()); 539 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { 540 if (entry.getValue().length > 0) { 541 result.put(entry.getKey(), entry.getValue()[0]); 542 } 543 } 544 return result; 545 } 546 } 547 548 @SuppressWarnings("unchecked") 549 private Object resolveRequestHeader(String headerName, boolean required, String defaultValue, 550 MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) 551 throws Exception { 552 553 Class<?> paramType = methodParam.getParameterType(); 554 if (Map.class.isAssignableFrom(paramType)) { 555 return resolveRequestHeaderMap((Class<? extends Map<?, ?>>) paramType, webRequest); 556 } 557 if (headerName.length() == 0) { 558 headerName = getRequiredParameterName(methodParam); 559 } 560 Object headerValue = null; 561 String[] headerValues = webRequest.getHeaderValues(headerName); 562 if (headerValues != null) { 563 headerValue = (headerValues.length == 1 ? headerValues[0] : headerValues); 564 } 565 if (headerValue == null) { 566 if (defaultValue != null) { 567 headerValue = resolveDefaultValue(defaultValue); 568 } 569 else if (required) { 570 raiseMissingHeaderException(headerName, paramType); 571 } 572 headerValue = checkValue(headerName, headerValue, paramType); 573 } 574 WebDataBinder binder = createBinder(webRequest, null, headerName); 575 initBinder(handlerForInitBinderCall, headerName, binder, webRequest); 576 return binder.convertIfNecessary(headerValue, paramType, methodParam); 577 } 578 579 private Map<String, ?> resolveRequestHeaderMap(Class<? extends Map<?, ?>> mapType, NativeWebRequest webRequest) { 580 if (MultiValueMap.class.isAssignableFrom(mapType)) { 581 MultiValueMap<String, String> result; 582 if (HttpHeaders.class.isAssignableFrom(mapType)) { 583 result = new HttpHeaders(); 584 } 585 else { 586 result = new LinkedMultiValueMap<String, String>(); 587 } 588 for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) { 589 String headerName = iterator.next(); 590 for (String headerValue : webRequest.getHeaderValues(headerName)) { 591 result.add(headerName, headerValue); 592 } 593 } 594 return result; 595 } 596 else { 597 Map<String, String> result = new LinkedHashMap<String, String>(); 598 for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) { 599 String headerName = iterator.next(); 600 String headerValue = webRequest.getHeader(headerName); 601 result.put(headerName, headerValue); 602 } 603 return result; 604 } 605 } 606 607 /** 608 * Resolves the given {@link RequestBody @RequestBody} annotation. 609 */ 610 protected Object resolveRequestBody(MethodParameter methodParam, NativeWebRequest webRequest, Object handler) 611 throws Exception { 612 613 return readWithMessageConverters(methodParam, createHttpInputMessage(webRequest), methodParam.getParameterType()); 614 } 615 616 private HttpEntity<?> resolveHttpEntityRequest(MethodParameter methodParam, NativeWebRequest webRequest) 617 throws Exception { 618 619 HttpInputMessage inputMessage = createHttpInputMessage(webRequest); 620 Class<?> paramType = getHttpEntityType(methodParam); 621 Object body = readWithMessageConverters(methodParam, inputMessage, paramType); 622 return new HttpEntity<Object>(body, inputMessage.getHeaders()); 623 } 624 625 @SuppressWarnings({ "unchecked", "rawtypes" }) 626 private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class<?> paramType) 627 throws Exception { 628 629 MediaType contentType = inputMessage.getHeaders().getContentType(); 630 if (contentType == null) { 631 StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType())); 632 String paramName = methodParam.getParameterName(); 633 if (paramName != null) { 634 builder.append(' '); 635 builder.append(paramName); 636 } 637 throw new HttpMediaTypeNotSupportedException( 638 "Cannot extract parameter (" + builder.toString() + "): no Content-Type found"); 639 } 640 641 List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); 642 if (this.messageConverters != null) { 643 for (HttpMessageConverter<?> messageConverter : this.messageConverters) { 644 allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); 645 if (messageConverter.canRead(paramType, contentType)) { 646 if (logger.isDebugEnabled()) { 647 logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType 648 +"\" using [" + messageConverter + "]"); 649 } 650 return messageConverter.read((Class) paramType, inputMessage); 651 } 652 } 653 } 654 throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); 655 } 656 657 private Class<?> getHttpEntityType(MethodParameter methodParam) { 658 Assert.isAssignable(HttpEntity.class, methodParam.getParameterType()); 659 ParameterizedType type = (ParameterizedType) methodParam.getGenericParameterType(); 660 if (type.getActualTypeArguments().length == 1) { 661 Type typeArgument = type.getActualTypeArguments()[0]; 662 if (typeArgument instanceof Class) { 663 return (Class<?>) typeArgument; 664 } 665 else if (typeArgument instanceof GenericArrayType) { 666 Type componentType = ((GenericArrayType) typeArgument).getGenericComponentType(); 667 if (componentType instanceof Class) { 668 // Surely, there should be a nicer way to do this 669 Object array = Array.newInstance((Class<?>) componentType, 0); 670 return array.getClass(); 671 } 672 } 673 } 674 throw new IllegalArgumentException( 675 "HttpEntity parameter (" + methodParam.getParameterName() + ") is not parameterized"); 676 677 } 678 679 private Object resolveCookieValue(String cookieName, boolean required, String defaultValue, 680 MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) 681 throws Exception { 682 683 Class<?> paramType = methodParam.getParameterType(); 684 if (cookieName.length() == 0) { 685 cookieName = getRequiredParameterName(methodParam); 686 } 687 Object cookieValue = resolveCookieValue(cookieName, paramType, webRequest); 688 if (cookieValue == null) { 689 if (defaultValue != null) { 690 cookieValue = resolveDefaultValue(defaultValue); 691 } 692 else if (required) { 693 raiseMissingCookieException(cookieName, paramType); 694 } 695 cookieValue = checkValue(cookieName, cookieValue, paramType); 696 } 697 WebDataBinder binder = createBinder(webRequest, null, cookieName); 698 initBinder(handlerForInitBinderCall, cookieName, binder, webRequest); 699 return binder.convertIfNecessary(cookieValue, paramType, methodParam); 700 } 701 702 /** 703 * Resolves the given {@link CookieValue @CookieValue} annotation. 704 * <p>Throws an UnsupportedOperationException by default. 705 */ 706 protected Object resolveCookieValue(String cookieName, Class<?> paramType, NativeWebRequest webRequest) 707 throws Exception { 708 709 throw new UnsupportedOperationException("@CookieValue not supported"); 710 } 711 712 private Object resolvePathVariable(String pathVarName, MethodParameter methodParam, 713 NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { 714 715 Class<?> paramType = methodParam.getParameterType(); 716 if (pathVarName.length() == 0) { 717 pathVarName = getRequiredParameterName(methodParam); 718 } 719 String pathVarValue = resolvePathVariable(pathVarName, paramType, webRequest); 720 WebDataBinder binder = createBinder(webRequest, null, pathVarName); 721 initBinder(handlerForInitBinderCall, pathVarName, binder, webRequest); 722 return binder.convertIfNecessary(pathVarValue, paramType, methodParam); 723 } 724 725 /** 726 * Resolves the given {@link PathVariable @PathVariable} annotation. 727 * <p>Throws an UnsupportedOperationException by default. 728 */ 729 protected String resolvePathVariable(String pathVarName, Class<?> paramType, NativeWebRequest webRequest) 730 throws Exception { 731 732 throw new UnsupportedOperationException("@PathVariable not supported"); 733 } 734 735 private String getRequiredParameterName(MethodParameter methodParam) { 736 String name = methodParam.getParameterName(); 737 if (name == null) { 738 throw new IllegalStateException( 739 "No parameter name specified for argument of type [" + methodParam.getParameterType().getName() + 740 "], and no parameter name information found in class file either."); 741 } 742 return name; 743 } 744 745 private Object checkValue(String name, Object value, Class<?> paramType) { 746 if (value == null) { 747 if (boolean.class == paramType) { 748 return Boolean.FALSE; 749 } 750 else if (paramType.isPrimitive()) { 751 throw new IllegalStateException("Optional " + paramType + " parameter '" + name + 752 "' is not present but cannot be translated into a null value due to being declared as a " + 753 "primitive type. Consider declaring it as object wrapper for the corresponding primitive type."); 754 } 755 } 756 return value; 757 } 758 759 private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam, 760 ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception { 761 762 // Bind request parameter onto object... 763 String name = attrName; 764 if ("".equals(name)) { 765 name = Conventions.getVariableNameForParameter(methodParam); 766 } 767 Class<?> paramType = methodParam.getParameterType(); 768 Object bindObject; 769 if (implicitModel.containsKey(name)) { 770 bindObject = implicitModel.get(name); 771 } 772 else if (this.methodResolver.isSessionAttribute(name, paramType)) { 773 bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name); 774 if (bindObject == null) { 775 raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session"); 776 } 777 } 778 else { 779 bindObject = BeanUtils.instantiateClass(paramType); 780 } 781 WebDataBinder binder = createBinder(webRequest, bindObject, name); 782 initBinder(handler, name, binder, webRequest); 783 return binder; 784 } 785 786 787 /** 788 * Determine whether the given value qualifies as a "binding candidate", i.e. might potentially be subject to 789 * bean-style data binding later on. 790 */ 791 protected boolean isBindingCandidate(Object value) { 792 return (value != null && !value.getClass().isArray() && !(value instanceof Collection) && 793 !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass())); 794 } 795 796 protected void raiseMissingParameterException(String paramName, Class<?> paramType) throws Exception { 797 throw new IllegalStateException("Missing parameter '" + paramName + "' of type [" + paramType.getName() + "]"); 798 } 799 800 protected void raiseMissingHeaderException(String headerName, Class<?> paramType) throws Exception { 801 throw new IllegalStateException("Missing header '" + headerName + "' of type [" + paramType.getName() + "]"); 802 } 803 804 protected void raiseMissingCookieException(String cookieName, Class<?> paramType) throws Exception { 805 throw new IllegalStateException( 806 "Missing cookie value '" + cookieName + "' of type [" + paramType.getName() + "]"); 807 } 808 809 protected void raiseSessionRequiredException(String message) throws Exception { 810 throw new IllegalStateException(message); 811 } 812 813 protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) 814 throws Exception { 815 816 return new WebRequestDataBinder(target, objectName); 817 } 818 819 private void doBind(WebDataBinder binder, NativeWebRequest webRequest, boolean validate, 820 Object[] validationHints, boolean failOnErrors) throws Exception { 821 822 doBind(binder, webRequest); 823 if (validate) { 824 binder.validate(validationHints); 825 } 826 if (failOnErrors && binder.getBindingResult().hasErrors()) { 827 throw new BindException(binder.getBindingResult()); 828 } 829 } 830 831 protected void doBind(WebDataBinder binder, NativeWebRequest webRequest) throws Exception { 832 ((WebRequestDataBinder) binder).bind(webRequest); 833 } 834 835 /** 836 * Return a {@link HttpInputMessage} for the given {@link NativeWebRequest}. 837 * <p>Throws an UnsupportedOperation1Exception by default. 838 */ 839 protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception { 840 throw new UnsupportedOperationException("@RequestBody not supported"); 841 } 842 843 /** 844 * Return a {@link HttpOutputMessage} for the given {@link NativeWebRequest}. 845 * <p>Throws an UnsupportedOperationException by default. 846 */ 847 protected HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) throws Exception { 848 throw new UnsupportedOperationException("@Body not supported"); 849 } 850 851 protected String parseDefaultValueAttribute(String value) { 852 return (ValueConstants.DEFAULT_NONE.equals(value) ? null : value); 853 } 854 855 protected Object resolveDefaultValue(String value) { 856 return value; 857 } 858 859 protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest) 860 throws Exception { 861 862 // Invoke custom argument resolvers if present... 863 if (this.customArgumentResolvers != null) { 864 for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) { 865 Object value = argumentResolver.resolveArgument(methodParameter, webRequest); 866 if (value != WebArgumentResolver.UNRESOLVED) { 867 return value; 868 } 869 } 870 } 871 872 // Resolution of standard parameter types... 873 Class<?> paramType = methodParameter.getParameterType(); 874 Object value = resolveStandardArgument(paramType, webRequest); 875 if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) { 876 throw new IllegalStateException("Standard argument type [" + paramType.getName() + 877 "] resolved to incompatible value of type [" + (value != null ? value.getClass() : null) + 878 "]. Consider declaring the argument type in a less specific fashion."); 879 } 880 return value; 881 } 882 883 protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception { 884 if (WebRequest.class.isAssignableFrom(parameterType)) { 885 return webRequest; 886 } 887 return WebArgumentResolver.UNRESOLVED; 888 } 889 890 protected final void addReturnValueAsModelAttribute(Method handlerMethod, Class<?> handlerType, 891 Object returnValue, ExtendedModelMap implicitModel) { 892 893 ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class); 894 String attrName = (attr != null ? attr.value() : ""); 895 if ("".equals(attrName)) { 896 Class<?> resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType); 897 attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue); 898 } 899 implicitModel.addAttribute(attrName, returnValue); 900 } 901 902}