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