001/*
002 * Copyright 2002-2019 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.config;
018
019import java.util.LinkedHashMap;
020import java.util.Map;
021
022import org.springframework.beans.factory.config.BeanDefinition;
023import org.springframework.beans.factory.config.RuntimeBeanReference;
024import org.springframework.beans.factory.parsing.BeanComponentDefinition;
025import org.springframework.beans.factory.support.RootBeanDefinition;
026import org.springframework.beans.factory.xml.ParserContext;
027import org.springframework.lang.Nullable;
028import org.springframework.util.AntPathMatcher;
029import org.springframework.util.PathMatcher;
030import org.springframework.web.cors.CorsConfiguration;
031import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
032import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
033import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
034import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
035import org.springframework.web.util.UrlPathHelper;
036
037/**
038 * Convenience methods for use in MVC namespace BeanDefinitionParsers.
039 *
040 * @author Rossen Stoyanchev
041 * @author Brian Clozel
042 * @since 3.1
043 */
044public abstract class MvcNamespaceUtils {
045
046        private static final String BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME =
047                        BeanNameUrlHandlerMapping.class.getName();
048
049        private static final String SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME =
050                        SimpleControllerHandlerAdapter.class.getName();
051
052        private static final String HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME =
053                        HttpRequestHandlerAdapter.class.getName();
054
055        private static final String URL_PATH_HELPER_BEAN_NAME = "mvcUrlPathHelper";
056
057        private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher";
058
059        private static final String CORS_CONFIGURATION_BEAN_NAME = "mvcCorsConfigurations";
060
061        private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
062
063
064        public static void registerDefaultComponents(ParserContext parserContext, @Nullable Object source) {
065                registerBeanNameUrlHandlerMapping(parserContext, source);
066                registerHttpRequestHandlerAdapter(parserContext, source);
067                registerSimpleControllerHandlerAdapter(parserContext, source);
068                registerHandlerMappingIntrospector(parserContext, source);
069        }
070
071        /**
072         * Adds an alias to an existing well-known name or registers a new instance of a {@link UrlPathHelper}
073         * under that well-known name, unless already registered.
074         * @return a RuntimeBeanReference to this {@link UrlPathHelper} instance
075         */
076        public static RuntimeBeanReference registerUrlPathHelper(
077                        @Nullable RuntimeBeanReference urlPathHelperRef, ParserContext parserContext, @Nullable Object source) {
078
079                if (urlPathHelperRef != null) {
080                        if (parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) {
081                                parserContext.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME);
082                        }
083                        parserContext.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME);
084                }
085                else if (!parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
086                                !parserContext.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) {
087                        RootBeanDefinition urlPathHelperDef = new RootBeanDefinition(UrlPathHelper.class);
088                        urlPathHelperDef.setSource(source);
089                        urlPathHelperDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
090                        parserContext.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef);
091                        parserContext.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME));
092                }
093                return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME);
094        }
095
096        /**
097         * Adds an alias to an existing well-known name or registers a new instance of a {@link PathMatcher}
098         * under that well-known name, unless already registered.
099         * @return a RuntimeBeanReference to this {@link PathMatcher} instance
100         */
101        public static RuntimeBeanReference registerPathMatcher(@Nullable RuntimeBeanReference pathMatcherRef,
102                        ParserContext parserContext, @Nullable Object source) {
103
104                if (pathMatcherRef != null) {
105                        if (parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
106                                parserContext.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
107                        }
108                        parserContext.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
109                }
110                else if (!parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
111                                !parserContext.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
112                        RootBeanDefinition pathMatcherDef = new RootBeanDefinition(AntPathMatcher.class);
113                        pathMatcherDef.setSource(source);
114                        pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
115                        parserContext.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef);
116                        parserContext.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME));
117                }
118                return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
119        }
120
121        /**
122         * Registers  an {@link HttpRequestHandlerAdapter} under a well-known
123         * name unless already registered.
124         */
125        private static void registerBeanNameUrlHandlerMapping(ParserContext context, @Nullable Object source) {
126                if (!context.getRegistry().containsBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME)) {
127                        RootBeanDefinition mappingDef = new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
128                        mappingDef.setSource(source);
129                        mappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
130                        mappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
131                        RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
132                        mappingDef.getPropertyValues().add("corsConfigurations", corsRef);
133                        context.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, mappingDef);
134                        context.registerComponent(new BeanComponentDefinition(mappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
135                }
136        }
137
138        /**
139         * Registers  an {@link HttpRequestHandlerAdapter} under a well-known
140         * name unless already registered.
141         */
142        private static void registerHttpRequestHandlerAdapter(ParserContext context, @Nullable Object source) {
143                if (!context.getRegistry().containsBeanDefinition(HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME)) {
144                        RootBeanDefinition adapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
145                        adapterDef.setSource(source);
146                        adapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
147                        context.getRegistry().registerBeanDefinition(HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME, adapterDef);
148                        context.registerComponent(new BeanComponentDefinition(adapterDef, HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME));
149                }
150        }
151
152        /**
153         * Registers a {@link SimpleControllerHandlerAdapter} under a well-known
154         * name unless already registered.
155         */
156        private static void registerSimpleControllerHandlerAdapter(ParserContext context, @Nullable Object source) {
157                if (!context.getRegistry().containsBeanDefinition(SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME)) {
158                        RootBeanDefinition beanDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
159                        beanDef.setSource(source);
160                        beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
161                        context.getRegistry().registerBeanDefinition(SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME, beanDef);
162                        context.registerComponent(new BeanComponentDefinition(beanDef, SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME));
163                }
164        }
165
166        /**
167         * Registers a {@code Map<String, CorsConfiguration>} (mapped {@code CorsConfiguration}s)
168         * under a well-known name unless already registered. The bean definition may be updated
169         * if a non-null CORS configuration is provided.
170         * @return a RuntimeBeanReference to this {@code Map<String, CorsConfiguration>} instance
171         */
172        public static RuntimeBeanReference registerCorsConfigurations(
173                        @Nullable Map<String, CorsConfiguration> corsConfigurations,
174                        ParserContext context, @Nullable Object source) {
175
176                if (!context.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) {
177                        RootBeanDefinition corsDef = new RootBeanDefinition(LinkedHashMap.class);
178                        corsDef.setSource(source);
179                        corsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
180                        if (corsConfigurations != null) {
181                                corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
182                        }
183                        context.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsDef);
184                        context.registerComponent(new BeanComponentDefinition(corsDef, CORS_CONFIGURATION_BEAN_NAME));
185                }
186                else if (corsConfigurations != null) {
187                        BeanDefinition corsDef = context.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);
188                        corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
189                }
190                return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
191        }
192
193        /**
194         * Registers  an {@link HandlerMappingIntrospector} under a well-known name
195         * unless already registered.
196         */
197        private static void registerHandlerMappingIntrospector(ParserContext parserContext, @Nullable Object source) {
198                if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
199                        RootBeanDefinition beanDef = new RootBeanDefinition(HandlerMappingIntrospector.class);
200                        beanDef.setSource(source);
201                        beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
202                        beanDef.setLazyInit(true);
203                        parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef);
204                        parserContext.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME));
205                }
206        }
207
208        /**
209         * Find the {@code ContentNegotiationManager} bean created by or registered
210         * with the {@code annotation-driven} element.
211         * @return a bean definition, bean reference, or {@code null} if none defined
212         */
213        @Nullable
214        public static Object getContentNegotiationManager(ParserContext context) {
215                String name = AnnotationDrivenBeanDefinitionParser.HANDLER_MAPPING_BEAN_NAME;
216                if (context.getRegistry().containsBeanDefinition(name)) {
217                        BeanDefinition handlerMappingBeanDef = context.getRegistry().getBeanDefinition(name);
218                        return handlerMappingBeanDef.getPropertyValues().get("contentNegotiationManager");
219                }
220                name = AnnotationDrivenBeanDefinitionParser.CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
221                if (context.getRegistry().containsBeanDefinition(name)) {
222                        return new RuntimeBeanReference(name);
223                }
224                return null;
225        }
226
227}