diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/src/main/resources/application.properties
index a9d87423d0b..caa1da3100f 100644
--- a/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/src/main/resources/application.properties
+++ b/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/src/main/resources/application.properties
@@ -1,3 +1,4 @@
server.port=8888
server.servlet.contextPath=/context
+vaadin.exclude-urls=/swagger-ui/**
diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/src/main/resources/application.properties
index 3b4370b224f..a643255ccc4 100644
--- a/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/src/main/resources/application.properties
+++ b/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/src/main/resources/application.properties
@@ -1,2 +1,3 @@
server.port=8888
vaadin.blacklisted-packages=vaadin-spring/target/test-classes
+vaadin.excludeUrls=/swagger-ui/**
diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/src/main/resources/application.properties
index a2cd7d0e32f..4ec6c2a7da4 100644
--- a/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/src/main/resources/application.properties
+++ b/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/src/main/resources/application.properties
@@ -1,3 +1,5 @@
server.port=8888
proxy.port=9999
proxy.path=/public/path
+vaadin.excludeUrls=/swagger-ui/**
+springdoc.swagger-ui.configUrl=/public/path/v3/api-docs/swagger-config
diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-scan/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-boot-scan/src/main/resources/application.properties
index c4e658f9e20..9627637e21c 100644
--- a/flow-tests/vaadin-spring-tests/test-spring-boot-scan/src/main/resources/application.properties
+++ b/flow-tests/vaadin-spring-tests/test-spring-boot-scan/src/main/resources/application.properties
@@ -1 +1,2 @@
server.port=8888
+vaadin.exclude-urls=/swagger-ui/**
diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-boot/src/main/resources/application.properties
index c4e658f9e20..9627637e21c 100644
--- a/flow-tests/vaadin-spring-tests/test-spring-boot/src/main/resources/application.properties
+++ b/flow-tests/vaadin-spring-tests/test-spring-boot/src/main/resources/application.properties
@@ -1 +1,2 @@
server.port=8888
+vaadin.exclude-urls=/swagger-ui/**
diff --git a/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml
index 4a08c90cf19..69c94042397 100644
--- a/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml
+++ b/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml
@@ -76,6 +76,24 @@
jaxb-impl
${jaxb.version}
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ 1.6.11
+
+
+ io.github.classgraph
+ classgraph
+ 4.8.149
+
+
+ org.yaml
+ snakeyaml
+ 1.33
+
+
diff --git a/flow-tests/vaadin-spring-tests/test-spring-common/src/test/java/com/vaadin/flow/spring/test/SwaggerIT.java b/flow-tests/vaadin-spring-tests/test-spring-common/src/test/java/com/vaadin/flow/spring/test/SwaggerIT.java
new file mode 100644
index 00000000000..fa841872451
--- /dev/null
+++ b/flow-tests/vaadin-spring-tests/test-spring-common/src/test/java/com/vaadin/flow/spring/test/SwaggerIT.java
@@ -0,0 +1,19 @@
+package com.vaadin.flow.spring.test;
+
+import org.junit.Test;
+
+public class SwaggerIT extends AbstractSpringTest {
+
+ @Override
+ protected String getTestPath() {
+ return "/swagger-ui.html";
+ }
+
+ @Test
+ public void swaggerUIShown() {
+ open();
+ waitUntil(
+ driver -> driver.getPageSource().contains("OpenAPI definition")
+ || driver.getPageSource().contains("Swagger Petstore"));
+ }
+}
diff --git a/flow-tests/vaadin-spring-tests/test-spring-war/src/main/resources/application.properties b/flow-tests/vaadin-spring-tests/test-spring-war/src/main/resources/application.properties
new file mode 100644
index 00000000000..fa25a35731f
--- /dev/null
+++ b/flow-tests/vaadin-spring-tests/test-spring-war/src/main/resources/application.properties
@@ -0,0 +1 @@
+vaadin.exclude-urls=/swagger-ui/**
diff --git a/flow-tests/vaadin-spring-tests/test-spring/pom.xml b/flow-tests/vaadin-spring-tests/test-spring/pom.xml
index 74a95d912ea..0822d3a34d3 100644
--- a/flow-tests/vaadin-spring-tests/test-spring/pom.xml
+++ b/flow-tests/vaadin-spring-tests/test-spring/pom.xml
@@ -1,6 +1,5 @@
-
+
4.0.0
com.vaadin
@@ -42,6 +41,12 @@
com.vaadin
vaadin-test-spring-common
${project.version}
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
@@ -103,6 +108,15 @@
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ **/SwaggerIT.java
+
+
+
org.eclipse.jetty
jetty-maven-plugin
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinConfigurationProperties.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinConfigurationProperties.java
index e1ab085b5fb..5686f664c9c 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinConfigurationProperties.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinConfigurationProperties.java
@@ -49,6 +49,22 @@ public static String getUrlMapping(Environment environment) {
.map(conf -> conf.getUrlMapping()).orElse(null);
}
+ /**
+ * Gets the excluded URLs using the given environment.
+ *
+ * This is needed only when VaadinConfigurationProperties is not available
+ * for injection, e.g. when using Spring without Boot.
+ *
+ * @param environment
+ * the application environment
+ * @return the excluded URLs or null if none is defined
+ */
+ public static List getExcludedUrls(Environment environment) {
+ return Binder.get(environment)
+ .bind("vaadin", VaadinConfigurationProperties.class)
+ .map(conf -> conf.getExcludeUrls()).orElse(null);
+ }
+
/**
* Base URL mapping of the Vaadin servlet.
*/
@@ -84,6 +100,12 @@ public static String getUrlMapping(Environment environment) {
*/
private boolean launchBrowser = false;
+ /**
+ * URL patterns that should not be handled by the Vaadin servlet when mapped
+ * to the context root.
+ */
+ private List excludeUrls;
+
public static class Pnpm {
private boolean enable;
@@ -261,4 +283,25 @@ public List getWhitelistedPackages() {
public void setWhitelistedPackages(List whitelistedPackages) {
this.whitelistedPackages = new ArrayList<>(whitelistedPackages);
}
+
+ /**
+ * Get a list of URL patterns that are not handled by the Vaadin servlet
+ * when it is mapped to the context root.
+ *
+ * @return a list of url patterns to exclude
+ */
+ public List getExcludeUrls() {
+ return excludeUrls;
+ }
+
+ /**
+ * Set a list of URL patterns that are not handled by the Vaadin servlet
+ * when it is mapped to the context root.
+ *
+ * @param excludeUrls
+ * a list of url patterns to exclude
+ */
+ public void setExcludeUrls(List excludeUrls) {
+ this.excludeUrls = excludeUrls;
+ }
}
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletConfiguration.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletConfiguration.java
index 3d49cddeb14..fef1f3531a4 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletConfiguration.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletConfiguration.java
@@ -15,17 +15,28 @@
*/
package com.vaadin.flow.spring;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
+import org.springframework.core.env.Environment;
+import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.ServletForwardingController;
+import org.springframework.web.util.UrlPathHelper;
import com.vaadin.flow.server.VaadinServlet;
@@ -47,6 +58,83 @@
public class VaadinServletConfiguration {
static final String VAADIN_SERVLET_MAPPING = "/vaadinServlet/*";
+ public static final String EXCLUDED_URLS_PROPERTY = "vaadin.excludeUrls";
+
+ /**
+ * Gets the excluded URLs in a way compatible with both plain Spring and
+ * Spring Boot.
+ *
+ * @param environment
+ * the application environment
+ * @return the excluded URLs or null if none is defined
+ */
+ private static List getExcludedUrls(Environment environment) {
+ if (SpringUtil.isSpringBoot()) {
+ try {
+ return (List) Class.forName(
+ "com.vaadin.flow.spring.VaadinConfigurationProperties")
+ .getMethod("getExcludedUrls", Environment.class)
+ .invoke(null, environment);
+ } catch (IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | NoSuchMethodException
+ | SecurityException | ClassNotFoundException e) {
+ LoggerFactory.getLogger(RootMappedCondition.class).error(
+ "Unable to find excluded URLs from properties", e);
+ return null;
+ }
+ } else {
+ String value = environment.getProperty(EXCLUDED_URLS_PROPERTY);
+ if (value == null || value.isEmpty()) {
+ return Collections.emptyList();
+ } else {
+ return Arrays.stream(value.split(",")).map(url -> url.trim())
+ .collect(Collectors.toList());
+ }
+ }
+ }
+
+ private static class RootExcludeHandler extends SimpleUrlHandlerMapping {
+ private List excludeUrls;
+ private AntPathMatcher matcher;
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
+
+ public RootExcludeHandler(List excludeUrls,
+ Controller vaadinForwardingController) {
+ this.excludeUrls = excludeUrls;
+ matcher = new AntPathMatcher();
+
+ setOrder(Ordered.LOWEST_PRECEDENCE - 1);
+
+ // This is /** and not /* so that it is not interpreted as a
+ // "default handler"
+ // and we can override the behavior in getHandlerInternal
+ setUrlMap(Collections.singletonMap("/**",
+ vaadinForwardingController));
+ }
+
+ @Override
+ protected Object getHandlerInternal(HttpServletRequest request)
+ throws Exception {
+ if (excludeUrls != null && !excludeUrls.isEmpty()) {
+ String requestPath = urlPathHelper
+ .getPathWithinApplication(request);
+ for (String pattern : excludeUrls) {
+ if (matcher.match(pattern, requestPath)) {
+ getLogger().debug(
+ "Ignoring request to {} excluded by {}",
+ requestPath, pattern);
+ return null;
+ }
+ }
+ }
+ return super.getHandlerInternal(request);
+ }
+
+ protected Logger getLogger() {
+ return LoggerFactory.getLogger(getClass());
+ }
+
+ }
/**
* Makes an url handler mapping allowing to forward requests from a
@@ -56,14 +144,9 @@ public class VaadinServletConfiguration {
* servlet
*/
@Bean
- public SimpleUrlHandlerMapping vaadinRootMapping() {
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- mapping.setOrder(Ordered.LOWEST_PRECEDENCE - 1);
-
- mapping.setUrlMap(
- Collections.singletonMap("/*", vaadinForwardingController()));
-
- return mapping;
+ public RootExcludeHandler vaadinRootMapping(Environment environment) {
+ return new RootExcludeHandler(getExcludedUrls(environment),
+ vaadinForwardingController());
}
/**
diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java
index 3998ec818a2..2658506ac5d 100644
--- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java
+++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java
@@ -79,6 +79,7 @@ protected Stream getExcludedPatterns() {
"com\\.vaadin\\.flow\\.spring\\.DispatcherServletRegistrationBeanConfig",
"com\\.vaadin\\.flow\\.spring\\.VaadinApplicationConfiguration",
"com\\.vaadin\\.flow\\.spring\\.VaadinServletConfiguration",
+ "com\\.vaadin\\.flow\\.spring\\.VaadinServletConfiguration\\$RootExcludeHandler",
"com\\.vaadin\\.flow\\.spring\\.VaadinScopesConfig",
"com\\.vaadin\\.flow\\.spring\\.VaadinSpringSecurity",
"com\\.vaadin\\.flow\\.spring\\.SpringBootAutoConfiguration",