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} — obtain a {@code HandlerMapping} 053 * to check request-matching criteria against. 054 * <li>{@link #getCorsConfiguration} — 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}