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