diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml
index 36e9e72a6cf..f4e3fc4631c 100644
--- a/spring-boot-autoconfigure/pom.xml
+++ b/spring-boot-autoconfigure/pom.xml
@@ -71,6 +71,11 @@
spring-web
true
+
+ org.springframework
+ spring-websocket
+ true
+
org.springframework
spring-webmvc
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/WebSocketAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/WebSocketAutoConfiguration.java
new file mode 100644
index 00000000000..6887a8f8db6
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/WebSocketAutoConfiguration.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012-2013 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.boot.autoconfigure.websocket;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletContainerInitializer;
+
+import org.apache.catalina.Context;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
+import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
+import org.springframework.web.socket.sockjs.SockJsService;
+import org.springframework.web.socket.sockjs.support.AbstractSockJsService;
+import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
+
+/**
+ * Auto configuration for websockets (and sockjs in particular). Users should be able to
+ * just define beans of type {@link WebSocketHandler}. If spring-websocket is
+ * detected on the classpath then we add a {@link DefaultSockJsService} and an MVC handler
+ * mapping to /<beanName>/** for all of the
+ * WebSocketHandler beans that have a bean name beginning with "/".
+ *
+ * @author Dave Syer
+ */
+@Configuration
+@ConditionalOnClass({ WebSocketHandler.class })
+@AutoConfigureBefore(EmbeddedServletContainerAutoConfiguration.class)
+public class WebSocketAutoConfiguration {
+
+ private static class WebSocketEndpointPostProcessor implements BeanPostProcessor {
+
+ private Map prefixes = new HashMap();
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName)
+ throws BeansException {
+ if (bean instanceof WebSocketHandler && beanName.startsWith("/")) {
+ this.prefixes.put(beanName, (WebSocketHandler) bean);
+ }
+ return bean;
+ }
+
+ public WebSocketHandler getHandler(String prefix) {
+ return this.prefixes.get(prefix);
+ }
+
+ public String[] getPrefixes() {
+ return this.prefixes.keySet().toArray(new String[this.prefixes.size()]);
+ }
+
+ }
+
+ @Bean
+ public WebSocketEndpointPostProcessor webSocketEndpointPostProcessor() {
+ return new WebSocketEndpointPostProcessor();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(SockJsService.class)
+ public DefaultSockJsService sockJsService() {
+ DefaultSockJsService service = new DefaultSockJsService(sockJsTaskScheduler());
+ service.setSockJsClientLibraryUrl("https://cdn.sockjs.org/sockjs-0.3.4.min.js");
+ service.setWebSocketsEnabled(true);
+ return service;
+ }
+
+ @Bean
+ public SimpleUrlHandlerMapping handlerMapping(SockJsService sockJsService,
+ Collection handlers) {
+
+ WebSocketEndpointPostProcessor processor = webSocketEndpointPostProcessor();
+ Map urlMap = new HashMap();
+ for (String prefix : webSocketEndpointPostProcessor().getPrefixes()) {
+ urlMap.put(prefix + "/**", new SockJsHttpRequestHandler(sockJsService,
+ processor.getHandler(prefix)));
+ }
+
+ if (sockJsService instanceof AbstractSockJsService) {
+ ((AbstractSockJsService) sockJsService).setValidSockJsPrefixes(processor
+ .getPrefixes());
+ }
+ SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
+ handlerMapping.setOrder(-1);
+ handlerMapping.setUrlMap(urlMap);
+
+ return handlerMapping;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(name = "sockJsTaskScheduler")
+ public ThreadPoolTaskScheduler sockJsTaskScheduler() {
+ ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+ taskScheduler.setThreadNamePrefix("SockJS-");
+ return taskScheduler;
+ }
+
+ @Configuration
+ @ConditionalOnClass(name = "org.apache.tomcat.websocket.server.WsSci")
+ protected static class TomcatWebSocketConfiguration {
+ @Bean
+ public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
+ TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory() {
+ @Override
+ protected void postProcessContext(Context context) {
+ context.addServletContainerInitializer(
+ (ServletContainerInitializer) BeanUtils
+ .instantiate(ClassUtils.resolveClassName(
+ "org.apache.tomcat.websocket.server.WsSci",
+ null)), null);
+ }
+ };
+ return factory;
+ }
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
index 311bbdf2311..1c157bdde39 100644
--- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -12,4 +12,5 @@ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
-org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration
+org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
+org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration
diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java
index 13cba880ada..17dd6fad976 100644
--- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java
+++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java
@@ -29,6 +29,7 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.OutputCapture;
+import org.springframework.boot.cli.command.CleanCommand;
import org.springframework.boot.cli.command.RunCommand;
import static org.junit.Assert.assertEquals;
@@ -66,8 +67,9 @@ public class SampleIntegrationTests {
}
@Before
- public void setup() {
+ public void setup() throws Exception {
System.setProperty("disableSpringSnapshotRepos", "true");
+ new CleanCommand().run("org.springframework");
}
@After
diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index 8aa48721708..ee7a9e4ee06 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -7,7 +7,7 @@
0.5.0.BUILD-SNAPSHOT
pom
- 4.0.0.M2
+ 4.0.0.BUILD-SNAPSHOT
3.2.0.M2
2.2.4.RELEASE
2.2.0.RELEASE
@@ -273,6 +273,11 @@
spring-tx
${spring.version}
+
+ org.springframework
+ spring-websocket
+ ${spring.version}
+
org.springframework
spring-web
diff --git a/spring-boot-integration-tests/pom.xml b/spring-boot-integration-tests/pom.xml
index 926a00f73f8..0889a50fb8a 100644
--- a/spring-boot-integration-tests/pom.xml
+++ b/spring-boot-integration-tests/pom.xml
@@ -119,6 +119,10 @@
src/it/settings.xml
${main.basedir}/spring-boot-samples/
+
+
+ spring-boot-sample-websocket/pom.xml
+
${project.build.directory}/local-repo
${skipTests}
diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index b0d670ebc24..2615b09a9a4 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -26,6 +26,7 @@
spring-boot-sample-traditional
spring-boot-sample-web-static
spring-boot-sample-web-ui
+ spring-boot-sample-websocket
spring-boot-sample-xml
diff --git a/spring-boot-samples/spring-boot-sample-websocket/_om.xml b/spring-boot-samples/spring-boot-sample-websocket/pom.xml
similarity index 64%
rename from spring-boot-samples/spring-boot-sample-websocket/_om.xml
rename to spring-boot-samples/spring-boot-sample-websocket/pom.xml
index 8651ffbc395..eac5cc7fe4a 100644
--- a/spring-boot-samples/spring-boot-sample-websocket/_om.xml
+++ b/spring-boot-samples/spring-boot-sample-websocket/pom.xml
@@ -14,8 +14,8 @@
1.7
- 8.0-SNAPSHOT
- org.springframework.boot.samples.websocket.config.ApplicationConfiguration
+ 8.0.0-RC1
+ org.springframework.boot.samples.websocket.config.SampleWebSocketsApplication
@@ -23,14 +23,10 @@
org.springframework.boot
spring-boot-starter-websocket
- ${spring.boot.version}
-
- org.eclipse.jetty.websocket
- websocket-client
- 9.0.3.v20130506
- test
+ org.springframework.boot
+ spring-boot-starter-actuator
@@ -43,17 +39,4 @@
-
-
- tomcat-snapshots
- https://repository.apache.org/content/repositories/snapshots
-
- true
-
-
- false
-
-
-
-
diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java
index f8f2aad3a56..2adde70e439 100644
--- a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java
+++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java
@@ -16,38 +16,24 @@
package org.springframework.boot.samples.websocket.config;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.catalina.Context;
-import org.apache.catalina.startup.Tomcat;
-import org.apache.tomcat.websocket.server.WsSci;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.samples.websocket.client.GreetingService;
import org.springframework.boot.samples.websocket.client.SimpleGreetingService;
import org.springframework.boot.samples.websocket.echo.DefaultEchoService;
import org.springframework.boot.samples.websocket.echo.EchoService;
import org.springframework.boot.samples.websocket.echo.EchoWebSocketHandler;
import org.springframework.boot.samples.websocket.snake.SnakeWebSocketHandler;
-import org.springframework.boot.web.SpringServletInitializer;
+import org.springframework.boot.web.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.web.servlet.DispatcherServlet;
-import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.socket.WebSocketHandler;
-import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
-import org.springframework.web.socket.sockjs.SockJsService;
-import org.springframework.web.socket.sockjs.support.DefaultSockJsService;
-import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import org.springframework.web.socket.support.PerConnectionWebSocketHandler;
@Configuration
-public class SampleWebSocketsApplication extends SpringServletInitializer {
-
+@EnableAutoConfiguration
+public class SampleWebSocketsApplication extends SpringBootServletInitializer {
+
@Override
protected Class>[] getConfigClasses() {
return new Class>[] { SampleWebSocketsApplication.class };
@@ -57,22 +43,6 @@ public class SampleWebSocketsApplication extends SpringServletInitializer {
SpringApplication.run(SampleWebSocketsApplication.class, args);
}
- @ConditionalOnClass(Tomcat.class)
- @Configuration
- @EnableAutoConfiguration
- protected static class InitializationConfiguration {
- @Bean
- public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
- TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory() {
- @Override
- protected void postProcessContext(Context context) {
- context.addServletContainerInitializer(new WsSci(), null);
- }
- };
- return factory;
- }
- }
-
@Bean
public EchoService echoService() {
return new DefaultEchoService("Did you say \"%s\"?");
@@ -83,47 +53,14 @@ public class SampleWebSocketsApplication extends SpringServletInitializer {
return new SimpleGreetingService();
}
- @Bean
- public SimpleUrlHandlerMapping handlerMapping() {
-
- SockJsService sockJsService = new DefaultSockJsService(sockJsTaskScheduler());
-
- Map urlMap = new HashMap();
-
- urlMap.put("/echo", new WebSocketHttpRequestHandler(echoWebSocketHandler()));
- urlMap.put("/snake", new WebSocketHttpRequestHandler(snakeWebSocketHandler()));
-
- urlMap.put("/sockjs/echo/**", new SockJsHttpRequestHandler(sockJsService, echoWebSocketHandler()));
- urlMap.put("/sockjs/snake/**", new SockJsHttpRequestHandler(sockJsService, snakeWebSocketHandler()));
-
- SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
- handlerMapping.setOrder(-1);
- handlerMapping.setUrlMap(urlMap);
-
- return handlerMapping;
- }
-
- @Bean
- public DispatcherServlet dispatcherServlet() {
- DispatcherServlet servlet = new DispatcherServlet();
- servlet.setDispatchOptionsRequest(true);
- return servlet;
- }
-
- @Bean
+ @Bean(name = "/echo")
public WebSocketHandler echoWebSocketHandler() {
return new PerConnectionWebSocketHandler(EchoWebSocketHandler.class);
}
- @Bean
+ @Bean(name = "/snake")
public WebSocketHandler snakeWebSocketHandler() {
return new SnakeWebSocketHandler();
}
- @Bean
- public ThreadPoolTaskScheduler sockJsTaskScheduler() {
- ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
- taskScheduler.setThreadNamePrefix("SockJS-");
- return taskScheduler;
- }
}
diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html
index cbaef719dd3..42081d4e196 100644
--- a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html
+++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html
@@ -49,6 +49,7 @@
margin: 0;
}
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+ ${spring.boot.version}
+
diff --git a/spring-boot-starters/spring-boot-starter-parent/src/main/maven/pom.xml b/spring-boot-starters/spring-boot-starter-parent/src/main/maven/pom.xml
index 90b5201ab95..6aaccd8048c 100644
--- a/spring-boot-starters/spring-boot-starter-parent/src/main/maven/pom.xml
+++ b/spring-boot-starters/spring-boot-starter-parent/src/main/maven/pom.xml
@@ -2,7 +2,7 @@
+ checkout of this file, be sure to modify 'spring-boot-starter-parent/src/main/parent/pom.xml'. -->
@@ -88,6 +88,11 @@
spring-boot-starter-security
${spring.boot.version}
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+ ${spring.boot.version}
+
diff --git a/spring-boot-starters/spring-boot-starter-websocket/pom.xml b/spring-boot-starters/spring-boot-starter-websocket/pom.xml
index 2181209b16e..2f4121cb362 100644
--- a/spring-boot-starters/spring-boot-starter-websocket/pom.xml
+++ b/spring-boot-starters/spring-boot-starter-websocket/pom.xml
@@ -11,7 +11,7 @@
jar
${basedir}/../..
- 8.0-SNAPSHOT
+ 8.0.0-RC1
@@ -35,14 +35,13 @@
${project.groupId}
- spring-boot-up-tomcat
+ spring-boot-starter-tomcat
org.springframework
spring-websocket
- ${spring.version}
org.apache.tomcat.embed
@@ -53,16 +52,4 @@
tomcat-embed-logging-juli
-
-
- tomcat-snapshots
- https://repository.apache.org/content/repositories/snapshots
-
- true
-
-
- false
-
-
-
diff --git a/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java b/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java
index f0abef730d7..119cdff6fdf 100644
--- a/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java
+++ b/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java
@@ -16,9 +16,11 @@
package org.springframework.boot;
+import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
@@ -26,7 +28,6 @@ import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@@ -49,7 +50,7 @@ import org.springframework.util.StringUtils;
*/
class BeanDefinitionLoader {
- private static final ResourceLoader DEFAULT_RESOURCE_LOADER = new DefaultResourceLoader();
+ private static final ResourceLoader DEFAULT_RESOURCE_LOADER = new PathMatchingResourcePatternResolver();
private Object[] sources;
@@ -153,22 +154,50 @@ class BeanDefinitionLoader {
}
private int load(CharSequence source) {
+ String sourceString = xmlReader.getEnvironment().resolvePlaceholders(source.toString());
try {
// Use class utils so that period separated nested class names work
- return load(ClassUtils.forName(source.toString(), null));
+ return load(ClassUtils.forName(sourceString, null));
}
catch (ClassNotFoundException ex) {
// swallow exception and continue
}
- Resource loadedResource = (this.resourceLoader != null ? this.resourceLoader
- : DEFAULT_RESOURCE_LOADER).getResource(source.toString());
- if (loadedResource != null && loadedResource.exists()) {
- return load(loadedResource);
+ ResourceLoader loader = this.resourceLoader != null ?
+ this.resourceLoader : DEFAULT_RESOURCE_LOADER;
+
+ int loadCount = 0;
+ if( loader instanceof ResourcePatternResolver ) {
+ // Resource pattern matching available.
+ try {
+ Resource[] resources = ((ResourcePatternResolver) loader).getResources(sourceString);
+ for(Resource resource : resources) {
+ if( resource.exists() ) {
+ loadCount += load(resource);
+ }
+ }
+ }
+ catch (IOException ex) {
+ throw new BeanDefinitionStoreException(
+ "Could not resolve bean definition resource pattern [" + sourceString + "]", ex);
+ }
}
- Package packageResource = findPackage(source);
- if (packageResource != null) {
- return load(packageResource);
+ if( !(loader instanceof ResourcePatternResolver) ) {
+ // Can only load single resources by absolute URL.
+ Resource loadedResource = loader.getResource(sourceString);
+ if (loadedResource != null && loadedResource.exists()) {
+ return load(loadedResource);
+ }
+ }
+ if( loadCount > 0 ) {
+ return loadCount;
+ }
+ else {
+ // Attempt to treat the source as a package name, common to all PatternResolver types
+ Package packageResource = findPackage(source);
+ if (packageResource != null) {
+ return load(packageResource);
+ }
}
throw new IllegalArgumentException("Invalid source '" + source + "'");
}
diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java
index 867d66aa78f..dec9443e550 100644
--- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java
+++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java
@@ -18,6 +18,7 @@ package org.springframework.boot.context.embedded.tomcat;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -34,6 +35,7 @@ import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.coyote.AbstractProtocol;
+import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
@@ -45,6 +47,7 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
/**
* {@link EmbeddedServletContainerFactory} that can be used to create
@@ -213,11 +216,7 @@ public class TomcatEmbeddedServletContainerFactory extends
context.getPipeline().addValve(valve);
}
for (ErrorPage errorPage : getErrorPages()) {
- org.apache.catalina.deploy.ErrorPage tomcatPage = new org.apache.catalina.deploy.ErrorPage();
- tomcatPage.setLocation(errorPage.getPath());
- tomcatPage.setExceptionType(errorPage.getExceptionName());
- tomcatPage.setErrorCode(errorPage.getStatusCode());
- context.addErrorPage(tomcatPage);
+ new TomcatErrorPage(errorPage).addToContext(context);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
@@ -380,4 +379,67 @@ public class TomcatEmbeddedServletContainerFactory extends
this.tomcatContextCustomizers.addAll(Arrays.asList(tomcatContextCustomizers));
}
+ private static class TomcatErrorPage {
+
+ private String location;
+ private String exceptionType;
+ private int errorCode;
+
+ private Object nativePage;
+
+ public TomcatErrorPage(ErrorPage errorPage) {
+ this.location = errorPage.getPath();
+ this.exceptionType = errorPage.getExceptionName();
+ this.errorCode = errorPage.getStatusCode();
+ this.nativePage = createNativePage(errorPage);
+ }
+
+ private Object createNativePage(ErrorPage errorPage) {
+ Object nativePage = null;
+ try {
+ if (ClassUtils.isPresent("org.apache.catalina.deploy.ErrorPage", null)) {
+ nativePage = new org.apache.catalina.deploy.ErrorPage();
+ }
+ else {
+ if (ClassUtils.isPresent(
+ "org.apache.tomcat.util.descriptor.web.ErrorPage", null)) {
+ nativePage = BeanUtils.instantiate(ClassUtils.forName(
+ "org.apache.tomcat.util.descriptor.web.ErrorPage", null));
+ }
+ }
+ }
+ catch (ClassNotFoundException e) {
+ }
+ catch (LinkageError e) {
+ }
+ return nativePage;
+ }
+
+ public void addToContext(Context context) {
+ Assert.state(this.nativePage != null,
+ "Neither Tomcat 7 nor 8 detected so no native error page exists");
+ if (ClassUtils.isPresent("org.apache.catalina.deploy.ErrorPage", null)) {
+ org.apache.catalina.deploy.ErrorPage errorPage = (org.apache.catalina.deploy.ErrorPage) this.nativePage;
+ errorPage.setLocation(this.location);
+ errorPage.setErrorCode(this.errorCode);
+ errorPage.setExceptionType(this.exceptionType);
+ context.addErrorPage(errorPage);
+ }
+ else {
+ callMethod(this.nativePage, "setLocation", this.location, String.class);
+ callMethod(this.nativePage, "setErrorCode", this.errorCode, int.class);
+ callMethod(this.nativePage, "setExceptionType", this.exceptionType,
+ String.class);
+ callMethod(context, "addErrorPage", this.nativePage,
+ this.nativePage.getClass());
+ }
+ }
+
+ private void callMethod(Object target, String name, Object value, Class> type) {
+ Method method = ReflectionUtils.findMethod(target.getClass(), name, type);
+ ReflectionUtils.invokeMethod(method, target, value);
+ }
+
+ }
+
}
diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java
index 0951d1f32c5..9ced5a6a846 100644
--- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java
+++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java
@@ -275,6 +275,14 @@ public class SpringApplicationTests {
application, "initialSources");
assertThat(initialSources.toArray(), equalTo(sources));
}
+
+ @Test
+ public void wildcardSources() {
+ Object[] sources = { "classpath:org/springframework/boot/sample-${sample.app.test.prop}.xml" };
+ TestSpringApplication application = new TestSpringApplication(sources);
+ application.setWebEnvironment(false);
+ application.run();
+ }
@Test
public void run() throws Exception {
diff --git a/spring-boot/src/test/resources/application.properties b/spring-boot/src/test/resources/application.properties
index 3fa3f5bae02..497e65a06b3 100644
--- a/spring-boot/src/test/resources/application.properties
+++ b/spring-boot/src/test/resources/application.properties
@@ -1 +1,2 @@
-foo: bucket
\ No newline at end of file
+foo: bucket
+sample.app.test.prop: *
\ No newline at end of file