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.servlet.mvc.method.annotation; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.Reader; 022import java.security.Principal; 023import java.time.ZoneId; 024import java.util.Locale; 025import java.util.TimeZone; 026 027import javax.servlet.ServletRequest; 028import javax.servlet.http.HttpServletRequest; 029import javax.servlet.http.HttpSession; 030import javax.servlet.http.PushBuilder; 031 032import org.springframework.core.MethodParameter; 033import org.springframework.http.HttpMethod; 034import org.springframework.lang.Nullable; 035import org.springframework.util.ClassUtils; 036import org.springframework.web.bind.support.WebDataBinderFactory; 037import org.springframework.web.context.request.NativeWebRequest; 038import org.springframework.web.context.request.WebRequest; 039import org.springframework.web.method.support.HandlerMethodArgumentResolver; 040import org.springframework.web.method.support.ModelAndViewContainer; 041import org.springframework.web.multipart.MultipartRequest; 042import org.springframework.web.servlet.support.RequestContextUtils; 043 044/** 045 * Resolves servlet backed request-related method arguments. Supports values of the 046 * following types: 047 * <ul> 048 * <li>{@link WebRequest} 049 * <li>{@link ServletRequest} 050 * <li>{@link MultipartRequest} 051 * <li>{@link HttpSession} 052 * <li>{@link PushBuilder} (as of Spring 5.0 on Servlet 4.0) 053 * <li>{@link Principal} 054 * <li>{@link InputStream} 055 * <li>{@link Reader} 056 * <li>{@link HttpMethod} (as of Spring 4.0) 057 * <li>{@link Locale} 058 * <li>{@link TimeZone} (as of Spring 4.0) 059 * <li>{@link java.time.ZoneId} (as of Spring 4.0 and Java 8) 060 * </ul> 061 * 062 * @author Arjen Poutsma 063 * @author Rossen Stoyanchev 064 * @author Juergen Hoeller 065 * @since 3.1 066 */ 067public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { 068 069 @Nullable 070 private static Class<?> pushBuilder; 071 072 static { 073 try { 074 pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder", 075 ServletRequestMethodArgumentResolver.class.getClassLoader()); 076 } 077 catch (ClassNotFoundException ex) { 078 // Servlet 4.0 PushBuilder not found - not supported for injection 079 pushBuilder = null; 080 } 081 } 082 083 084 @Override 085 public boolean supportsParameter(MethodParameter parameter) { 086 Class<?> paramType = parameter.getParameterType(); 087 return (WebRequest.class.isAssignableFrom(paramType) || 088 ServletRequest.class.isAssignableFrom(paramType) || 089 MultipartRequest.class.isAssignableFrom(paramType) || 090 HttpSession.class.isAssignableFrom(paramType) || 091 (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || 092 Principal.class.isAssignableFrom(paramType) || 093 InputStream.class.isAssignableFrom(paramType) || 094 Reader.class.isAssignableFrom(paramType) || 095 HttpMethod.class == paramType || 096 Locale.class == paramType || 097 TimeZone.class == paramType || 098 ZoneId.class == paramType); 099 } 100 101 @Override 102 public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, 103 NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { 104 105 Class<?> paramType = parameter.getParameterType(); 106 107 // WebRequest / NativeWebRequest / ServletWebRequest 108 if (WebRequest.class.isAssignableFrom(paramType)) { 109 if (!paramType.isInstance(webRequest)) { 110 throw new IllegalStateException( 111 "Current request is not of type [" + paramType.getName() + "]: " + webRequest); 112 } 113 return webRequest; 114 } 115 116 // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest 117 if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) { 118 return resolveNativeRequest(webRequest, paramType); 119 } 120 121 // HttpServletRequest required for all further argument types 122 return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class)); 123 } 124 125 private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) { 126 T nativeRequest = webRequest.getNativeRequest(requiredType); 127 if (nativeRequest == null) { 128 throw new IllegalStateException( 129 "Current request is not of type [" + requiredType.getName() + "]: " + webRequest); 130 } 131 return nativeRequest; 132 } 133 134 @Nullable 135 private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException { 136 if (HttpSession.class.isAssignableFrom(paramType)) { 137 HttpSession session = request.getSession(); 138 if (session != null && !paramType.isInstance(session)) { 139 throw new IllegalStateException( 140 "Current session is not of type [" + paramType.getName() + "]: " + session); 141 } 142 return session; 143 } 144 else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) { 145 return PushBuilderDelegate.resolvePushBuilder(request, paramType); 146 } 147 else if (InputStream.class.isAssignableFrom(paramType)) { 148 InputStream inputStream = request.getInputStream(); 149 if (inputStream != null && !paramType.isInstance(inputStream)) { 150 throw new IllegalStateException( 151 "Request input stream is not of type [" + paramType.getName() + "]: " + inputStream); 152 } 153 return inputStream; 154 } 155 else if (Reader.class.isAssignableFrom(paramType)) { 156 Reader reader = request.getReader(); 157 if (reader != null && !paramType.isInstance(reader)) { 158 throw new IllegalStateException( 159 "Request body reader is not of type [" + paramType.getName() + "]: " + reader); 160 } 161 return reader; 162 } 163 else if (Principal.class.isAssignableFrom(paramType)) { 164 Principal userPrincipal = request.getUserPrincipal(); 165 if (userPrincipal != null && !paramType.isInstance(userPrincipal)) { 166 throw new IllegalStateException( 167 "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal); 168 } 169 return userPrincipal; 170 } 171 else if (HttpMethod.class == paramType) { 172 return HttpMethod.resolve(request.getMethod()); 173 } 174 else if (Locale.class == paramType) { 175 return RequestContextUtils.getLocale(request); 176 } 177 else if (TimeZone.class == paramType) { 178 TimeZone timeZone = RequestContextUtils.getTimeZone(request); 179 return (timeZone != null ? timeZone : TimeZone.getDefault()); 180 } 181 else if (ZoneId.class == paramType) { 182 TimeZone timeZone = RequestContextUtils.getTimeZone(request); 183 return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault()); 184 } 185 186 // Should never happen... 187 throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName()); 188 } 189 190 191 /** 192 * Inner class to avoid a hard dependency on Servlet API 4.0 at runtime. 193 */ 194 private static class PushBuilderDelegate { 195 196 @Nullable 197 public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) { 198 PushBuilder pushBuilder = request.newPushBuilder(); 199 if (pushBuilder != null && !paramType.isInstance(pushBuilder)) { 200 throw new IllegalStateException( 201 "Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder); 202 } 203 return pushBuilder; 204 205 } 206 } 207 208}