diff --git a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java
index 09beea747d..5f9047c144 100644
--- a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java
+++ b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2020 the original author or authors.
+ * Copyright 2014-2021 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.
@@ -37,7 +37,7 @@ import org.springframework.web.socket.sockjs.transport.TransportHandler;
/**
* The {@link IntegrationWebSocketContainer} implementation for the {@code server}
- * {@link org.springframework.web.socket.WebSocketHandler} registration.
+ * {@link WebSocketHandler} registration.
*
* Registers an internal {@code IntegrationWebSocketContainer.IntegrationWebSocketHandler}
* for provided {@link #paths} with the {@link WebSocketHandlerRegistry}.
@@ -69,6 +69,8 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer
private int phase = 0;
+ private TaskScheduler sockJsTaskScheduler;
+
public ServerWebSocketContainer(String... paths) {
Assert.notEmpty(paths, "'paths' must not be empty");
this.paths = Arrays.copyOf(paths, paths.length);
@@ -118,11 +120,11 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer
public ServerWebSocketContainer withSockJs(SockJsServiceOptions... sockJsServiceOptions) {
if (ObjectUtils.isEmpty(sockJsServiceOptions)) {
- this.sockJsServiceOptions = new SockJsServiceOptions();
+ setSockJsServiceOptions(new SockJsServiceOptions());
}
else {
Assert.state(sockJsServiceOptions.length == 1, "Only one 'sockJsServiceOptions' is applicable.");
- this.sockJsServiceOptions = sockJsServiceOptions[0];
+ setSockJsServiceOptions(sockJsServiceOptions[0]);
}
return this;
}
@@ -131,6 +133,21 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer
this.sockJsServiceOptions = sockJsServiceOptions;
}
+ /**
+ * Configure a {@link TaskScheduler} for SockJS fallback service.
+ * This is an alternative for default SockJS service scheduler
+ * when Websocket endpoint (this server container) is registered at runtime.
+ * @param sockJsTaskScheduler the {@link TaskScheduler} for SockJS fallback service.
+ * @since 5.5.1
+ */
+ public void setSockJsTaskScheduler(TaskScheduler sockJsTaskScheduler) {
+ this.sockJsTaskScheduler = sockJsTaskScheduler;
+ }
+
+ public TaskScheduler getSockJsTaskScheduler() {
+ return this.sockJsTaskScheduler;
+ }
+
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
WebSocketHandler webSocketHandler = this.webSocketHandler;
@@ -153,6 +170,8 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer
if (this.sockJsServiceOptions != null) {
SockJsServiceRegistration sockJsServiceRegistration = registration.withSockJS();
JavaUtils.INSTANCE
+ .acceptIfCondition(this.sockJsServiceOptions.taskScheduler == null,
+ this.sockJsTaskScheduler, this.sockJsServiceOptions::setTaskScheduler)
.acceptIfNotNull(this.sockJsServiceOptions.webSocketEnabled,
sockJsServiceRegistration::setWebSocketEnabled)
.acceptIfNotNull(this.sockJsServiceOptions.clientLibraryUrl,
@@ -226,7 +245,7 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer
}
/**
- * @see org.springframework.web.socket.config.annotation.SockJsServiceRegistration
+ * @see SockJsServiceRegistration
*/
public static class SockJsServiceOptions {
diff --git a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationDynamicWebSocketHandlerMapping.java b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationDynamicWebSocketHandlerMapping.java
index b7e018bfa0..dc59ac0dcb 100644
--- a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationDynamicWebSocketHandlerMapping.java
+++ b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationDynamicWebSocketHandlerMapping.java
@@ -16,14 +16,23 @@
package org.springframework.integration.websocket.config;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.http.server.PathContainer;
+import org.springframework.http.server.RequestPath;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
+import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
+import org.springframework.web.util.ServletRequestPathUtils;
+import org.springframework.web.util.pattern.PathPattern;
+import org.springframework.web.util.pattern.PathPatternParser;
/**
* The {@link AbstractHandlerMapping} implementation for dynamic WebSocket endpoint registrations in Spring Integration.
@@ -34,23 +43,60 @@ import org.springframework.web.servlet.handler.AbstractHandlerMapping;
*
* @since 5.5
*/
-class IntegrationDynamicWebSocketHandlerMapping extends AbstractHandlerMapping {
+class IntegrationDynamicWebSocketHandlerMapping extends AbstractUrlHandlerMapping {
private final Map handlerMap = new HashMap<>();
+ private final Map pathPatternHandlerMap = new LinkedHashMap<>();
+
@Override
protected Object getHandlerInternal(HttpServletRequest request) {
String lookupPath = initLookupPath(request);
HttpRequestHandler httpRequestHandler = this.handlerMap.get(lookupPath);
+ if (httpRequestHandler == null && usesPathPatterns()) {
+ RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
+ return lookupByPattern(path);
+ }
return httpRequestHandler != null ? new HandlerExecutionChain(httpRequestHandler) : null;
}
+ private Object lookupByPattern(RequestPath path) {
+ List matches = null;
+ for (PathPattern pattern : this.pathPatternHandlerMap.keySet()) {
+ if (pattern.matches(path.pathWithinApplication())) {
+ matches = (matches != null ? matches : new ArrayList<>());
+ matches.add(pattern);
+ }
+ }
+ if (matches == null) {
+ return null;
+ }
+ if (matches.size() > 1) {
+ matches.sort(PathPattern.SPECIFICITY_COMPARATOR);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Matching patterns " + matches);
+ }
+ }
+ PathPattern pattern = matches.get(0);
+ HttpRequestHandler handler = this.pathPatternHandlerMap.get(pattern);
+ PathContainer pathWithinMapping = pattern.extractPathWithinPattern(path.pathWithinApplication());
+ return buildPathExposingHandler(handler, pattern.getPatternString(), pathWithinMapping.value(), null);
+ }
+
void registerHandler(String path, HttpRequestHandler httpHandler) {
this.handlerMap.put(path, httpHandler);
+ PathPatternParser patternParser = getPatternParser();
+ if (patternParser != null) {
+ this.pathPatternHandlerMap.put(patternParser.parse(path), httpHandler);
+ }
}
void unregisterHandler(String path) {
this.handlerMap.remove(path);
+ PathPatternParser patternParser = getPatternParser();
+ if (patternParser != null) {
+ this.pathPatternHandlerMap.remove(patternParser.parse(path));
+ }
}
}
diff --git a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationServletWebSocketHandlerRegistry.java b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationServletWebSocketHandlerRegistry.java
index 898d9b72c2..8c486f5abd 100644
--- a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationServletWebSocketHandlerRegistry.java
+++ b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/IntegrationServletWebSocketHandlerRegistry.java
@@ -46,10 +46,14 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistra
class IntegrationServletWebSocketHandlerRegistry extends ServletWebSocketHandlerRegistry
implements ApplicationContextAware, DestructionAwareBeanPostProcessor {
+ private final ThreadLocal currentRegistration = new ThreadLocal<>();
+
private final Map> dynamicRegistrations = new HashMap<>();
private ApplicationContext applicationContext;
+ private TaskScheduler sockJsTaskScheduler;
+
private volatile IntegrationDynamicWebSocketHandlerMapping dynamicHandlerMapping;
IntegrationServletWebSocketHandlerRegistry() {
@@ -60,15 +64,10 @@ class IntegrationServletWebSocketHandlerRegistry extends ServletWebSocketHandler
this.applicationContext = applicationContext;
}
-
@Override
- protected boolean requiresTaskScheduler() { // NOSONAR visibility
- return super.requiresTaskScheduler();
- }
-
- @Override
- protected void setTaskScheduler(TaskScheduler scheduler) { // NOSONAR visibility
+ protected void setTaskScheduler(TaskScheduler scheduler) {
super.setTaskScheduler(scheduler);
+ this.sockJsTaskScheduler = scheduler;
}
@Override
@@ -85,15 +84,7 @@ class IntegrationServletWebSocketHandlerRegistry extends ServletWebSocketHandler
IntegrationDynamicWebSocketHandlerRegistration registration =
new IntegrationDynamicWebSocketHandlerRegistration();
registration.addHandler(handler, paths);
- MultiValueMap mappings = registration.getMapping();
- for (Map.Entry> entry : mappings.entrySet()) {
- HttpRequestHandler httpHandler = entry.getKey();
- List patterns = entry.getValue();
- this.dynamicRegistrations.put(handler, patterns);
- for (String pattern : patterns) {
- this.dynamicHandlerMapping.registerHandler(pattern, httpHandler);
- }
- }
+ this.currentRegistration.set(registration);
return registration;
}
else {
@@ -102,9 +93,24 @@ class IntegrationServletWebSocketHandlerRegistry extends ServletWebSocketHandler
}
@Override
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (this.dynamicHandlerMapping != null && bean instanceof ServerWebSocketContainer) {
- ((ServerWebSocketContainer) bean).registerWebSocketHandlers(this);
+ ServerWebSocketContainer serverWebSocketContainer = (ServerWebSocketContainer) bean;
+ if (serverWebSocketContainer.getSockJsTaskScheduler() == null) {
+ serverWebSocketContainer.setSockJsTaskScheduler(this.sockJsTaskScheduler);
+ }
+ serverWebSocketContainer.registerWebSocketHandlers(this);
+ IntegrationDynamicWebSocketHandlerRegistration registration = this.currentRegistration.get();
+ this.currentRegistration.remove();
+ MultiValueMap mappings = registration.getMapping();
+ for (Map.Entry> entry : mappings.entrySet()) {
+ HttpRequestHandler httpHandler = entry.getKey();
+ List patterns = entry.getValue();
+ this.dynamicRegistrations.put(registration.handler, patterns);
+ for (String pattern : patterns) {
+ this.dynamicHandlerMapping.registerHandler(pattern, httpHandler);
+ }
+ }
}
return bean;
}
@@ -133,6 +139,15 @@ class IntegrationServletWebSocketHandlerRegistry extends ServletWebSocketHandler
private static final class IntegrationDynamicWebSocketHandlerRegistration
extends ServletWebSocketHandlerRegistration {
+ private WebSocketHandler handler;
+
+ @Override
+ public WebSocketHandlerRegistration addHandler(WebSocketHandler handler, String... paths) {
+ // The IntegrationWebSocketContainer comes only with a single WebSocketHandler
+ this.handler = handler;
+ return super.addHandler(handler, paths);
+ }
+
MultiValueMap getMapping() {
return getMappings();
}
diff --git a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/WebSocketIntegrationConfigurationInitializer.java b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/WebSocketIntegrationConfigurationInitializer.java
index c50af9ea47..11864abd15 100644
--- a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/WebSocketIntegrationConfigurationInitializer.java
+++ b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/WebSocketIntegrationConfigurationInitializer.java
@@ -35,6 +35,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.socket.config.annotation.DelegatingWebSocketConfiguration;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.util.pattern.PathPatternParser;
/**
* The WebSocket Integration infrastructure {@code beanFactory} initializer.
@@ -102,6 +103,7 @@ public class WebSocketIntegrationConfigurationInitializer implements Integration
() -> {
IntegrationDynamicWebSocketHandlerMapping dynamicWebSocketHandlerMapping =
new IntegrationDynamicWebSocketHandlerMapping();
+ dynamicWebSocketHandlerMapping.setPatternParser(new PathPatternParser());
dynamicWebSocketHandlerMapping.setOrder(0);
return dynamicWebSocketHandlerMapping;
}),
@@ -143,9 +145,7 @@ public class WebSocketIntegrationConfigurationInitializer implements Integration
.values()
.forEach(configurer -> configurer.registerWebSocketHandlers(this.registry));
}
- if (this.registry.requiresTaskScheduler()) {
- this.registry.setTaskScheduler(this.sockJsTaskScheduler);
- }
+ this.registry.setTaskScheduler(this.sockJsTaskScheduler);
return this.registry.getHandlerMapping();
}
diff --git a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/dsl/WebSocketDslTests.java b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/dsl/WebSocketDslTests.java
index 307fb13db0..8cdf2c7cf9 100644
--- a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/dsl/WebSocketDslTests.java
+++ b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/dsl/WebSocketDslTests.java
@@ -69,7 +69,8 @@ public class WebSocketDslTests {
IntegrationFlowContext serverIntegrationFlowContext = serverContext.getBean(IntegrationFlowContext.class);
ServerWebSocketContainer serverWebSocketContainer =
new ServerWebSocketContainer("/dynamic")
- .setHandshakeHandler(serverContext.getBean(HandshakeHandler.class));
+ .setHandshakeHandler(serverContext.getBean(HandshakeHandler.class))
+ .withSockJs();
WebSocketInboundChannelAdapter webSocketInboundChannelAdapter =
new WebSocketInboundChannelAdapter(serverWebSocketContainer);
@@ -88,7 +89,7 @@ public class WebSocketDslTests {
// Dynamic client flow
ClientWebSocketContainer clientWebSocketContainer =
- new ClientWebSocketContainer(this.webSocketClient, this.server.getWsBaseUrl() + "/dynamic");
+ new ClientWebSocketContainer(this.webSocketClient, this.server.getWsBaseUrl() + "/dynamic/websocket");
clientWebSocketContainer.setAutoStartup(true);
WebSocketOutboundMessageHandler webSocketOutboundMessageHandler =
new WebSocketOutboundMessageHandler(clientWebSocketContainer);