From e5f76af193d1ea4c57c1a0a7af0d8a77da2cd75f Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Tue, 2 Jun 2015 16:11:56 +0200 Subject: [PATCH] Support CORS global configuration in XML namespace This commit introduces support for this kind of CORS XML namespace configuration: Issue: SPR-13046 --- .../AnnotationDrivenBeanDefinitionParser.java | 3 + .../config/CorsBeanDefinitionParser.java | 120 ++++++++++++++++++ .../servlet/config/MvcNamespaceHandler.java | 1 + .../web/servlet/config/MvcNamespaceUtils.java | 32 +++++ .../config/ResourcesBeanDefinitionParser.java | 3 + .../ViewControllerBeanDefinitionParser.java | 3 + .../web/servlet/config/spring-mvc-4.2.xsd | 83 ++++++++++++ .../web/servlet/config/MvcNamespaceTests.java | 89 ++++++++++--- .../config/mvc-config-cors-minimal.xml | 13 ++ .../web/servlet/config/mvc-config-cors.xml | 20 +++ 10 files changed, 350 insertions(+), 17 deletions(-) create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java create mode 100644 spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors-minimal.xml create mode 100644 spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index dca386dfe3a..c7c78a96216 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -197,6 +197,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { configurePathMatchingProperties(handlerMappingDef, element, parserContext); + RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source); + handlerMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef); + RuntimeBeanReference conversionService = getConversionService(element, source, parserContext); RuntimeBeanReference validator = getValidator(element, source, parserContext); RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java new file mode 100644 index 00000000000..fa70a9e7b96 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.config; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.http.HttpMethod; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.springframework.web.cors.CorsConfiguration; + +/** + * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a + * {@code cors} element in order to set the CORS configuration in the various + * {AbstractHandlerMapping} beans created by {@link AnnotationDrivenBeanDefinitionParser}, + * {@link ResourcesBeanDefinitionParser} and {@link ViewControllerBeanDefinitionParser}. + * + * @author Sebastien Deleuze + * @since 4.2 + */ +public class CorsBeanDefinitionParser implements BeanDefinitionParser { + + private static final List DEFAULT_ALLOWED_ORIGINS = Arrays.asList("*"); + + private static final List DEFAULT_ALLOWED_METHODS = + Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()); + + private static final List DEFAULT_ALLOWED_HEADERS = Arrays.asList("*"); + + private static final boolean DEFAULT_ALLOW_CREDENTIALS = true; + + private static final long DEFAULT_MAX_AGE = 1600; + + + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + + Map corsConfigurations = new LinkedHashMap(); + List mappings = DomUtils.getChildElementsByTagName(element, "mapping"); + + if (mappings.isEmpty()) { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(DEFAULT_ALLOWED_ORIGINS); + config.setAllowedMethods(DEFAULT_ALLOWED_METHODS); + config.setAllowedHeaders(DEFAULT_ALLOWED_HEADERS); + config.setAllowCredentials(DEFAULT_ALLOW_CREDENTIALS); + config.setMaxAge(DEFAULT_MAX_AGE); + corsConfigurations.put("/**", config); + } + else { + for (Element mapping : mappings) { + CorsConfiguration config = new CorsConfiguration(); + if (mapping.hasAttribute("allowed-origins")) { + String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ","); + config.setAllowedOrigins(Arrays.asList(allowedOrigins)); + } + else { + config.setAllowedOrigins(DEFAULT_ALLOWED_ORIGINS); + } + if (mapping.hasAttribute("allowed-methods")) { + String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ","); + config.setAllowedMethods(Arrays.asList(allowedMethods)); + } + else { + config.setAllowedMethods(DEFAULT_ALLOWED_METHODS); + } + if (mapping.hasAttribute("allowed-headers")) { + String[] allowedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-headers"), ","); + config.setAllowedHeaders(Arrays.asList(allowedHeaders)); + } + else { + config.setAllowedHeaders(DEFAULT_ALLOWED_HEADERS); + } + if (mapping.hasAttribute("exposed-headers")) { + String[] exposedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("exposed-headers"), ","); + config.setExposedHeaders(Arrays.asList(exposedHeaders)); + } + if (mapping.hasAttribute("allow-credentials")) { + config.setAllowCredentials(Boolean.parseBoolean(mapping.getAttribute("allow-credentials"))); + } + else { + config.setAllowCredentials(DEFAULT_ALLOW_CREDENTIALS); + } + if (mapping.hasAttribute("max-age")) { + config.setMaxAge(Long.parseLong(mapping.getAttribute("max-age"))); + } + else { + config.setMaxAge(DEFAULT_MAX_AGE); + } + corsConfigurations.put(mapping.getAttribute("path"), config); + } + } + + MvcNamespaceUtils.registerCorsConfiguration(corsConfigurations, parserContext, parserContext.extractSource(element)); + return null; + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java index c4305f02887..1cfe910855c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java @@ -44,6 +44,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser()); + registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser()); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java index 30e1688d4a0..5b36371d4c5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java @@ -16,6 +16,9 @@ package org.springframework.web.servlet.config; +import java.util.LinkedHashMap; +import java.util.Map; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; @@ -23,6 +26,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; +import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; @@ -50,6 +54,8 @@ abstract class MvcNamespaceUtils { private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher"; + private static final String CORS_CONFIGURATION_BEAN_NAME = "mvcCorsConfigurations"; + public static void registerDefaultComponents(ParserContext parserContext, Object source) { registerBeanNameUrlHandlerMapping(parserContext, source); @@ -113,6 +119,8 @@ abstract class MvcNamespaceUtils { beanNameMappingDef.setSource(source); beanNameMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); beanNameMappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport + RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source); + beanNameMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef); parserContext.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, beanNameMappingDef); parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME)); } @@ -146,4 +154,28 @@ abstract class MvcNamespaceUtils { } } + /** + * Registers a {@code Map} (mapped {@code CorsConfiguration}s) + * under a well-known name unless already registered. The bean definition may be updated + * if a non-null CORS configuration is provided. + * @return a RuntimeBeanReference to this {@code Map} instance + */ + public static RuntimeBeanReference registerCorsConfiguration(Map corsConfiguration, ParserContext parserContext, Object source) { + if (!parserContext.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) { + RootBeanDefinition corsConfigurationsDef = new RootBeanDefinition(LinkedHashMap.class); + corsConfigurationsDef.setSource(source); + corsConfigurationsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + if (corsConfiguration != null) { + corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfiguration); + } + parserContext.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsConfigurationsDef); + parserContext.registerComponent(new BeanComponentDefinition(corsConfigurationsDef, CORS_CONFIGURATION_BEAN_NAME)); + } + else if (corsConfiguration != null) { + BeanDefinition corsConfigurationsDef = parserContext.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME); + corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfiguration); + } + return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java index 02fbde89220..9ad4c4ce980 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java @@ -116,6 +116,9 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser { // Use a default of near-lowest precedence, still allowing for even lower precedence in other mappings handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1); + RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source); + handlerMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef); + String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef); parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef); parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java index 4e847cfd983..2843435784c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java @@ -22,6 +22,7 @@ import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -126,6 +127,8 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser { beanDef.getPropertyValues().add("order", "1"); beanDef.getPropertyValues().add("pathMatcher", MvcNamespaceUtils.registerPathMatcher(null, context, source)); beanDef.getPropertyValues().add("urlPathHelper", MvcNamespaceUtils.registerUrlPathHelper(null, context, source)); + RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, context, source); + beanDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef); return beanDef; } diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd index 671014d4780..01808861848 100644 --- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd +++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd @@ -1234,4 +1234,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 92032a31961..dca9ce92306 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -84,6 +84,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter; import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.InvocableHandlerMethod; @@ -91,6 +92,7 @@ import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.handler.AbstractHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; import org.springframework.web.servlet.handler.MappedInterceptor; @@ -187,7 +189,7 @@ public class MvcNamespaceTests { @Test public void testDefaultConfig() throws Exception { - loadBeanDefinitions("mvc-config.xml", 13); + loadBeanDefinitions("mvc-config.xml", 14); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -250,7 +252,7 @@ public class MvcNamespaceTests { @Test(expected = TypeMismatchException.class) public void testCustomConversionService() throws Exception { - loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 13); + loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 14); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -285,7 +287,7 @@ public class MvcNamespaceTests { } private void doTestCustomValidator(String xml) throws Exception { - loadBeanDefinitions(xml, 13); + loadBeanDefinitions(xml, 14); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -307,7 +309,7 @@ public class MvcNamespaceTests { @Test public void testInterceptors() throws Exception { - loadBeanDefinitions("mvc-config-interceptors.xml", 20); + loadBeanDefinitions("mvc-config-interceptors.xml", 21); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -343,7 +345,7 @@ public class MvcNamespaceTests { @Test public void testResources() throws Exception { - loadBeanDefinitions("mvc-config-resources.xml", 9); + loadBeanDefinitions("mvc-config-resources.xml", 10); HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class); assertNotNull(adapter); @@ -383,7 +385,7 @@ public class MvcNamespaceTests { @Test public void testResourcesWithOptionalAttributes() throws Exception { - loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 9); + loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 10); SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class); assertNotNull(mapping); @@ -400,7 +402,7 @@ public class MvcNamespaceTests { @Test public void testResourcesWithResolversTransformers() throws Exception { - loadBeanDefinitions("mvc-config-resources-chain.xml", 10); + loadBeanDefinitions("mvc-config-resources-chain.xml", 11); SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class); assertNotNull(mapping); @@ -439,7 +441,7 @@ public class MvcNamespaceTests { @Test public void testResourcesWithResolversTransformersCustom() throws Exception { - loadBeanDefinitions("mvc-config-resources-chain-no-auto.xml", 11); + loadBeanDefinitions("mvc-config-resources-chain-no-auto.xml", 12); SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class); assertNotNull(mapping); @@ -472,7 +474,7 @@ public class MvcNamespaceTests { @Test public void testDefaultServletHandler() throws Exception { - loadBeanDefinitions("mvc-config-default-servlet.xml", 5); + loadBeanDefinitions("mvc-config-default-servlet.xml", 6); HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class); assertNotNull(adapter); @@ -498,7 +500,7 @@ public class MvcNamespaceTests { @Test public void testDefaultServletHandlerWithOptionalAttributes() throws Exception { - loadBeanDefinitions("mvc-config-default-servlet-optional-attrs.xml", 5); + loadBeanDefinitions("mvc-config-default-servlet-optional-attrs.xml", 6); HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class); assertNotNull(adapter); @@ -524,7 +526,7 @@ public class MvcNamespaceTests { @Test public void testBeanDecoration() throws Exception { - loadBeanDefinitions("mvc-config-bean-decoration.xml", 15); + loadBeanDefinitions("mvc-config-bean-decoration.xml", 16); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -545,7 +547,7 @@ public class MvcNamespaceTests { @Test public void testViewControllers() throws Exception { - loadBeanDefinitions("mvc-config-view-controllers.xml", 18); + loadBeanDefinitions("mvc-config-view-controllers.xml", 19); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -626,7 +628,7 @@ public class MvcNamespaceTests { /** WebSphere gives trailing servlet path slashes by default!! */ @Test public void testViewControllersOnWebSphere() throws Exception { - loadBeanDefinitions("mvc-config-view-controllers.xml", 18); + loadBeanDefinitions("mvc-config-view-controllers.xml", 19); SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class); @@ -670,7 +672,7 @@ public class MvcNamespaceTests { @Test public void testViewControllersDefaultConfig() { - loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 6); + loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 7); SimpleUrlHandlerMapping hm = this.appContext.getBean(SimpleUrlHandlerMapping.class); assertNotNull(hm); @@ -693,7 +695,7 @@ public class MvcNamespaceTests { @Test public void testContentNegotiationManager() throws Exception { - loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 13); + loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 14); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); ContentNegotiationManager manager = mapping.getContentNegotiationManager(); @@ -705,7 +707,7 @@ public class MvcNamespaceTests { @Test public void testAsyncSupportOptions() throws Exception { - loadBeanDefinitions("mvc-config-async-support.xml", 14); + loadBeanDefinitions("mvc-config-async-support.xml", 15); RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class); assertNotNull(adapter); @@ -845,7 +847,7 @@ public class MvcNamespaceTests { @Test public void testPathMatchingHandlerMappings() throws Exception { - loadBeanDefinitions("mvc-config-path-matching-mappings.xml", 22); + loadBeanDefinitions("mvc-config-path-matching-mappings.xml", 23); RequestMappingHandlerMapping requestMapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(requestMapping); @@ -864,6 +866,59 @@ public class MvcNamespaceTests { } } + @Test + public void testCorsMinimal() throws Exception { + loadBeanDefinitions("mvc-config-cors-minimal.xml", 14); + + String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class); + assertEquals(2, beanNames.length); + for (String beanName : beanNames) { + AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName); + assertNotNull(handlerMapping); + Map configs = handlerMapping.getCorsConfiguration(); + assertNotNull(configs); + assertEquals(1, configs.size()); + CorsConfiguration config = configs.get("/**"); + assertNotNull(config); + assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[]{"GET", "HEAD", "POST"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray()); + assertNull(config.getExposedHeaders()); + assertTrue(config.getAllowCredentials()); + assertEquals(new Long(1600), config.getMaxAge()); + } + } + + @Test + public void testCors() throws Exception { + loadBeanDefinitions("mvc-config-cors.xml", 14); + + String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class); + assertEquals(2, beanNames.length); + for (String beanName : beanNames) { + AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName); + assertNotNull(handlerMapping); + Map configs = handlerMapping.getCorsConfiguration(); + assertNotNull(configs); + assertEquals(2, configs.size()); + CorsConfiguration config = configs.get("/api/**"); + assertNotNull(config); + assertArrayEquals(new String[]{"http://domain1.com", "http://domain2.com"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[]{"GET", "PUT"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[]{"header1", "header2", "header3"}, config.getAllowedHeaders().toArray()); + assertArrayEquals(new String[]{"header1", "header2"}, config.getExposedHeaders().toArray()); + assertFalse(config.getAllowCredentials()); + assertEquals(new Long(123), config.getMaxAge()); + config = configs.get("/resources/**"); + assertArrayEquals(new String[]{"http://domain1.com"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[]{"GET", "HEAD", "POST"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray()); + assertNull(config.getExposedHeaders()); + assertTrue(config.getAllowCredentials()); + assertEquals(new Long(1600), config.getMaxAge()); + } + } + private void loadBeanDefinitions(String fileName, int expectedBeanCount) { XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors-minimal.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors-minimal.xml new file mode 100644 index 00000000000..3e5ba455a1a --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors-minimal.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml new file mode 100644 index 00000000000..2d84e479e28 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + +