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.handler;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024import java.util.Properties;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletRequestWrapper;
027
028import org.springframework.beans.factory.BeanFactoryUtils;
029import org.springframework.beans.factory.InitializingBean;
030import org.springframework.context.ApplicationContext;
031import org.springframework.context.ApplicationContextAware;
032import org.springframework.core.annotation.AnnotationAwareOrderComparator;
033import org.springframework.core.io.ClassPathResource;
034import org.springframework.core.io.Resource;
035import org.springframework.core.io.support.PropertiesLoaderUtils;
036import org.springframework.util.Assert;
037import org.springframework.util.ClassUtils;
038import org.springframework.util.StringUtils;
039import org.springframework.web.cors.CorsConfiguration;
040import org.springframework.web.cors.CorsConfigurationSource;
041import org.springframework.web.servlet.DispatcherServlet;
042import org.springframework.web.servlet.HandlerExecutionChain;
043import org.springframework.web.servlet.HandlerInterceptor;
044import org.springframework.web.servlet.HandlerMapping;
045
046/**
047 * Helper class to get information from the {@code HandlerMapping} that would
048 * serve a specific request.
049 *
050 * <p>Provides the following methods:
051 * <ul>
052 * <li>{@link #getMatchableHandlerMapping} &mdash; obtain a {@code HandlerMapping}
053 * to check request-matching criteria against.
054 * <li>{@link #getCorsConfiguration} &mdash; obtain the CORS configuration for the
055 * request.
056 * </ul>
057 *
058 * @author Rossen Stoyanchev
059 * @since 4.3.1
060 */
061public class HandlerMappingIntrospector
062                implements CorsConfigurationSource, ApplicationContextAware, InitializingBean {
063
064        private ApplicationContext applicationContext;
065
066        private List<HandlerMapping> handlerMappings;
067
068
069        /**
070         * Constructor for use with {@link ApplicationContextAware}.
071         */
072        public HandlerMappingIntrospector() {
073        }
074
075        /**
076         * Constructor that detects the configured {@code HandlerMapping}s in the
077         * given {@code ApplicationContext} or falls back on
078         * "DispatcherServlet.properties" like the {@code DispatcherServlet}.
079         * @deprecated as of 4.3.12, in favor of {@link #setApplicationContext}
080         */
081        @Deprecated
082        public HandlerMappingIntrospector(ApplicationContext context) {
083                this.handlerMappings = initHandlerMappings(context);
084        }
085
086
087        /**
088         * Return the configured HandlerMapping's.
089         */
090        public List<HandlerMapping> getHandlerMappings() {
091                return (this.handlerMappings != null ? this.handlerMappings : Collections.<HandlerMapping>emptyList());
092        }
093
094
095        @Override
096        public void setApplicationContext(ApplicationContext applicationContext) {
097                this.applicationContext = applicationContext;
098        }
099
100        @Override
101        public void afterPropertiesSet() {
102                if (this.handlerMappings == null) {
103                        Assert.notNull(this.applicationContext, "No ApplicationContext");
104                        this.handlerMappings = initHandlerMappings(this.applicationContext);
105                }
106        }
107
108
109        /**
110         * Find the {@link HandlerMapping} that would handle the given request and
111         * return it as a {@link MatchableHandlerMapping} that can be used to test
112         * request-matching criteria.
113         * <p>If the matching HandlerMapping is not an instance of
114         * {@link MatchableHandlerMapping}, an IllegalStateException is raised.
115         * @param request the current request
116         * @return the resolved matcher, or {@code null}
117         * @throws Exception if any of the HandlerMapping's raise an exception
118         */
119        public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
120                Assert.notNull(this.handlerMappings, "Handler mappings not initialized");
121                HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
122                for (HandlerMapping handlerMapping : this.handlerMappings) {
123                        Object handler = handlerMapping.getHandler(wrapper);
124                        if (handler == null) {
125                                continue;
126                        }
127                        if (handlerMapping instanceof MatchableHandlerMapping) {
128                                return ((MatchableHandlerMapping) handlerMapping);
129                        }
130                        throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
131                }
132                return null;
133        }
134
135        @Override
136        public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
137                Assert.notNull(this.handlerMappings, "Handler mappings not initialized");
138                HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
139                for (HandlerMapping handlerMapping : this.handlerMappings) {
140                        HandlerExecutionChain handler = null;
141                        try {
142                                handler = handlerMapping.getHandler(wrapper);
143                        }
144                        catch (Exception ex) {
145                                // Ignore
146                        }
147                        if (handler == null) {
148                                continue;
149                        }
150                        if (handler.getInterceptors() != null) {
151                                for (HandlerInterceptor interceptor : handler.getInterceptors()) {
152                                        if (interceptor instanceof CorsConfigurationSource) {
153                                                return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);
154                                        }
155                                }
156                        }
157                        if (handler.getHandler() instanceof CorsConfigurationSource) {
158                                return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);
159                        }
160                }
161                return null;
162        }
163
164
165        private static List<HandlerMapping> initHandlerMappings(ApplicationContext applicationContext) {
166                Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
167                                applicationContext, HandlerMapping.class, true, false);
168                if (!beans.isEmpty()) {
169                        List<HandlerMapping> mappings = new ArrayList<HandlerMapping>(beans.values());
170                        AnnotationAwareOrderComparator.sort(mappings);
171                        return Collections.unmodifiableList(mappings);
172                }
173                return Collections.unmodifiableList(initFallback(applicationContext));
174        }
175
176        private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
177                Properties props;
178                String path = "DispatcherServlet.properties";
179                try {
180                        Resource resource = new ClassPathResource(path, DispatcherServlet.class);
181                        props = PropertiesLoaderUtils.loadProperties(resource);
182                }
183                catch (IOException ex) {
184                        throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
185                }
186
187                String value = props.getProperty(HandlerMapping.class.getName());
188                String[] names = StringUtils.commaDelimitedListToStringArray(value);
189                List<HandlerMapping> result = new ArrayList<HandlerMapping>(names.length);
190                for (String name : names) {
191                        try {
192                                Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
193                                Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
194                                result.add((HandlerMapping) mapping);
195                        }
196                        catch (ClassNotFoundException ex) {
197                                throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
198                        }
199                }
200                return result;
201        }
202
203
204        /**
205         * Request wrapper that ignores request attribute changes.
206         */
207        private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
208
209                public RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
210                        super(request);
211                }
212
213                @Override
214                public void setAttribute(String name, Object value) {
215                        // Ignore attribute change...
216                }
217        }
218
219}