001/* 002 * Copyright 2002-2017 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.servlet.mvc.method.annotation; 018 019import java.util.Collections; 020import java.util.Map; 021import javax.servlet.ServletRequest; 022 023import org.springframework.core.MethodParameter; 024import org.springframework.core.convert.ConversionService; 025import org.springframework.core.convert.TypeDescriptor; 026import org.springframework.core.convert.converter.Converter; 027import org.springframework.util.StringUtils; 028import org.springframework.validation.DataBinder; 029import org.springframework.web.bind.ServletRequestDataBinder; 030import org.springframework.web.bind.WebDataBinder; 031import org.springframework.web.bind.support.WebDataBinderFactory; 032import org.springframework.web.context.request.NativeWebRequest; 033import org.springframework.web.context.request.RequestAttributes; 034import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; 035import org.springframework.web.servlet.HandlerMapping; 036 037/** 038 * A Servlet-specific {@link ModelAttributeMethodProcessor} that applies data 039 * binding through a WebDataBinder of type {@link ServletRequestDataBinder}. 040 * 041 * <p>Also adds a fall-back strategy to instantiate the model attribute from a 042 * URI template variable or from a request parameter if the name matches the 043 * model attribute name and there is an appropriate type conversion strategy. 044 * 045 * @author Rossen Stoyanchev 046 * @author Juergen Hoeller 047 * @since 3.1 048 */ 049public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor { 050 051 /** 052 * Class constructor. 053 * @param annotationNotRequired if "true", non-simple method arguments and 054 * return values are considered model attributes with or without a 055 * {@code @ModelAttribute} annotation 056 */ 057 public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) { 058 super(annotationNotRequired); 059 } 060 061 062 /** 063 * Instantiate the model attribute from a URI template variable or from a 064 * request parameter if the name matches to the model attribute name and 065 * if there is an appropriate type conversion strategy. If none of these 066 * are true delegate back to the base class. 067 * @see #createAttributeFromRequestValue 068 */ 069 @Override 070 protected final Object createAttribute(String attributeName, MethodParameter parameter, 071 WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { 072 073 String value = getRequestValueForAttribute(attributeName, request); 074 if (value != null) { 075 Object attribute = createAttributeFromRequestValue( 076 value, attributeName, parameter, binderFactory, request); 077 if (attribute != null) { 078 return attribute; 079 } 080 } 081 082 return super.createAttribute(attributeName, parameter, binderFactory, request); 083 } 084 085 /** 086 * Obtain a value from the request that may be used to instantiate the 087 * model attribute through type conversion from String to the target type. 088 * <p>The default implementation looks for the attribute name to match 089 * a URI variable first and then a request parameter. 090 * @param attributeName the model attribute name 091 * @param request the current request 092 * @return the request value to try to convert, or {@code null} if none 093 */ 094 protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) { 095 Map<String, String> variables = getUriTemplateVariables(request); 096 String variableValue = variables.get(attributeName); 097 if (StringUtils.hasText(variableValue)) { 098 return variableValue; 099 } 100 String parameterValue = request.getParameter(attributeName); 101 if (StringUtils.hasText(parameterValue)) { 102 return parameterValue; 103 } 104 return null; 105 } 106 107 @SuppressWarnings("unchecked") 108 protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) { 109 Map<String, String> variables = (Map<String, String>) request.getAttribute( 110 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); 111 return (variables != null ? variables : Collections.<String, String>emptyMap()); 112 } 113 114 /** 115 * Create a model attribute from a String request value (e.g. URI template 116 * variable, request parameter) using type conversion. 117 * <p>The default implementation converts only if there a registered 118 * {@link Converter} that can perform the conversion. 119 * @param sourceValue the source value to create the model attribute from 120 * @param attributeName the name of the attribute (never {@code null}) 121 * @param parameter the method parameter 122 * @param binderFactory for creating WebDataBinder instance 123 * @param request the current request 124 * @return the created model attribute, or {@code null} if no suitable 125 * conversion found 126 */ 127 protected Object createAttributeFromRequestValue(String sourceValue, String attributeName, 128 MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) 129 throws Exception { 130 131 DataBinder binder = binderFactory.createBinder(request, null, attributeName); 132 ConversionService conversionService = binder.getConversionService(); 133 if (conversionService != null) { 134 TypeDescriptor source = TypeDescriptor.valueOf(String.class); 135 TypeDescriptor target = new TypeDescriptor(parameter); 136 if (conversionService.canConvert(source, target)) { 137 return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter); 138 } 139 } 140 return null; 141 } 142 143 /** 144 * This implementation downcasts {@link WebDataBinder} to 145 * {@link ServletRequestDataBinder} before binding. 146 * @see ServletRequestDataBinderFactory 147 */ 148 @Override 149 protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { 150 ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); 151 ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; 152 servletBinder.bind(servletRequest); 153 } 154 155}