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}