Add SimpUserRegistry with multi-server support
This change introduces SimpUserRegistry exposing an API to access information about connected users, their sessions, and subscriptions with STOMP/WebSocket messaging. Provides are methods to access users as well as a method to find subscriptions given a Matcher strategy. The DefaultSimpUserRegistry implementation is also a SmartApplicationListener which listesn for ApplicationContext events when users connect, disconnect, subscribe, and unsubscribe to destinations. The MultiServerUserRegistry implementation is a composite that aggregates user information from the local SimpUserRegistry as well as snapshots of user on remote application servers. UserRegistryMessageHandler is used with MultiServerUserRegistry. It broadcats user registry information through the broker and listens for similar broadcasts from other servers. This must be enabled explicitly when configuring the STOMP broker relay. The existing UserSessionRegistry which was primiarly used internally to resolve a user name to session id's has been deprecated and is no longer used. If an application configures a custom UserSessionRegistr still, it will be adapted accordingly to SimpUserRegistry but the effect is rather limited (comparable to pre-existing functionality) and will not work in multi-server scenarios. Issue: SPR-12029
This commit is contained in:
@@ -17,7 +17,6 @@
|
||||
package org.springframework.messaging.simp.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -43,14 +42,16 @@ import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
|
||||
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
||||
import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
|
||||
import org.springframework.messaging.simp.user.DefaultUserSessionRegistry;
|
||||
import org.springframework.messaging.simp.user.MultiServerUserRegistry;
|
||||
import org.springframework.messaging.simp.user.SimpUserRegistry;
|
||||
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
|
||||
import org.springframework.messaging.simp.user.UserDestinationResolver;
|
||||
import org.springframework.messaging.simp.user.UserSessionRegistry;
|
||||
import org.springframework.messaging.simp.user.UserRegistryMessageHandler;
|
||||
import org.springframework.messaging.support.AbstractSubscribableChannel;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.PathMatcher;
|
||||
@@ -88,14 +89,14 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
"com.fasterxml.jackson.databind.ObjectMapper", AbstractMessageBrokerConfiguration.class.getClassLoader());
|
||||
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ChannelRegistration clientInboundChannelRegistration;
|
||||
|
||||
private ChannelRegistration clientOutboundChannelRegistration;
|
||||
|
||||
private MessageBrokerRegistry brokerRegistry;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* Protected constructor.
|
||||
@@ -287,12 +288,16 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
if (handler == null) {
|
||||
return new NoOpBrokerMessageHandler();
|
||||
}
|
||||
Map<String, MessageHandler> subscriptions = new HashMap<String, MessageHandler>(1);
|
||||
String destination = getBrokerRegistry().getUserDestinationBroadcast();
|
||||
if (destination != null) {
|
||||
Map<String, MessageHandler> map = new HashMap<String, MessageHandler>(1);
|
||||
map.put(destination, userDestinationMessageHandler());
|
||||
handler.setSystemSubscriptions(map);
|
||||
subscriptions.put(destination, userDestinationMessageHandler());
|
||||
}
|
||||
destination = getBrokerRegistry().getUserRegistryBroadcast();
|
||||
if (destination != null) {
|
||||
subscriptions.put(destination, userRegistryMessageHandler());
|
||||
}
|
||||
handler.setSystemSubscriptions(subscriptions);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@@ -301,10 +306,30 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(clientInboundChannel(),
|
||||
brokerChannel(), userDestinationResolver());
|
||||
String destination = getBrokerRegistry().getUserDestinationBroadcast();
|
||||
handler.setUserDestinationBroadcast(destination);
|
||||
handler.setBroadcastDestination(destination);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageHandler userRegistryMessageHandler() {
|
||||
if (getBrokerRegistry().getUserRegistryBroadcast() == null) {
|
||||
return new NoOpMessageHandler();
|
||||
}
|
||||
return new UserRegistryMessageHandler(userRegistry(), brokerMessagingTemplate(),
|
||||
getBrokerRegistry().getUserRegistryBroadcast(), messageBrokerTaskScheduler());
|
||||
}
|
||||
|
||||
// Expose alias for 4.1 compatibility
|
||||
|
||||
@Bean(name={"messageBrokerTaskScheduler", "messageBrokerSockJsTaskScheduler"})
|
||||
public ThreadPoolTaskScheduler messageBrokerTaskScheduler() {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setThreadNamePrefix("MessageBroker-");
|
||||
scheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
|
||||
scheduler.setRemoveOnCancelPolicy(true);
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpMessagingTemplate brokerMessagingTemplate() {
|
||||
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel());
|
||||
@@ -350,7 +375,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
|
||||
@Bean
|
||||
public UserDestinationResolver userDestinationResolver() {
|
||||
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userSessionRegistry());
|
||||
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry());
|
||||
String prefix = getBrokerRegistry().getUserDestinationPrefix();
|
||||
if (prefix != null) {
|
||||
resolver.setUserDestinationPrefix(prefix);
|
||||
@@ -359,8 +384,24 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserSessionRegistry userSessionRegistry() {
|
||||
return new DefaultUserSessionRegistry();
|
||||
@SuppressWarnings("deprecation")
|
||||
public SimpUserRegistry userRegistry() {
|
||||
return (getBrokerRegistry().getUserRegistryBroadcast() != null ?
|
||||
new MultiServerUserRegistry(createLocalUserRegistry()) : createLocalUserRegistry());
|
||||
}
|
||||
|
||||
protected abstract SimpUserRegistry createLocalUserRegistry();
|
||||
|
||||
/**
|
||||
* As of 4.2, UserSessionRegistry is deprecated in favor of SimpUserRegistry
|
||||
* exposing information about all connected users. The MultiServerUserRegistry
|
||||
* implementation in combination with UserRegistryMessageHandler can be used
|
||||
* to share user registries across multiple servers.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
protected org.springframework.messaging.simp.user.UserSessionRegistry userSessionRegistry() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,6 +458,14 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
}
|
||||
|
||||
|
||||
private static class NoOpMessageHandler implements MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class NoOpBrokerMessageHandler extends AbstractBrokerMessageHandler {
|
||||
|
||||
public NoOpBrokerMessageHandler() {
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
||||
import org.springframework.messaging.simp.user.SimpUserRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
@@ -49,8 +50,6 @@ public class MessageBrokerRegistry {
|
||||
|
||||
private String userDestinationPrefix;
|
||||
|
||||
private String userDestinationBroadcast;
|
||||
|
||||
private PathMatcher pathMatcher;
|
||||
|
||||
|
||||
@@ -139,22 +138,14 @@ public class MessageBrokerRegistry {
|
||||
return this.userDestinationPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a destination to broadcast messages to that remain unresolved because
|
||||
* the user is not connected. In a multi-application server scenario this
|
||||
* gives other application servers a chance to try.
|
||||
* <p><strong>Note:</strong> this option applies only when the
|
||||
* {@link #enableStompBrokerRelay "broker relay"} is enabled.
|
||||
* <p>By default this is not set.
|
||||
* @param destination the destination to forward unresolved
|
||||
* messages to, e.g. "/topic/unresolved-user-destination".
|
||||
*/
|
||||
public void setUserDestinationBroadcast(String destination) {
|
||||
this.userDestinationBroadcast = destination;
|
||||
protected String getUserDestinationBroadcast() {
|
||||
return (this.brokerRelayRegistration != null ?
|
||||
this.brokerRelayRegistration.getUserDestinationBroadcast() : null);
|
||||
}
|
||||
|
||||
protected String getUserDestinationBroadcast() {
|
||||
return this.userDestinationBroadcast;
|
||||
protected String getUserRegistryBroadcast() {
|
||||
return (this.brokerRelayRegistration != null ?
|
||||
this.brokerRelayRegistration.getUserRegistryBroadcast() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,10 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
|
||||
|
||||
private boolean autoStartup = true;
|
||||
|
||||
private String userDestinationBroadcast;
|
||||
|
||||
private String userRegistryBroadcast;
|
||||
|
||||
|
||||
public StompBrokerRelayRegistration(SubscribableChannel clientInboundChannel,
|
||||
MessageChannel clientOutboundChannel, String[] destinationPrefixes) {
|
||||
@@ -166,10 +170,48 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a destination to broadcast messages to user destinations that remain
|
||||
* unresolved because the user appears not to be connected. In a
|
||||
* multi-application server scenario this gives other application servers
|
||||
* a chance to try.
|
||||
* <p>By default this is not set.
|
||||
* @param destination the destination to broadcast unresolved messages to,
|
||||
* e.g. "/topic/unresolved-user-destination"
|
||||
*/
|
||||
public StompBrokerRelayRegistration setUserDestinationBroadcast(String destination) {
|
||||
this.userDestinationBroadcast = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected String getUserDestinationBroadcast() {
|
||||
return this.userDestinationBroadcast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a destination to broadcast the content of the local user registry to
|
||||
* and to listen for such broadcasts from other servers. In a multi-application
|
||||
* server scenarios this allows each server's user registry to be aware of
|
||||
* users connected to other servers.
|
||||
* <p>By default this is not set.
|
||||
* @param destination the destination for broadcasting user registry details,
|
||||
* e.g. "/topic/simp-user-registry".
|
||||
*/
|
||||
public StompBrokerRelayRegistration setUserRegistryBroadcast(String destination) {
|
||||
this.userRegistryBroadcast = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected String getUserRegistryBroadcast() {
|
||||
return this.userRegistryBroadcast;
|
||||
}
|
||||
|
||||
|
||||
protected StompBrokerRelayMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
|
||||
StompBrokerRelayMessageHandler handler = new StompBrokerRelayMessageHandler(getClientInboundChannel(),
|
||||
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes());
|
||||
|
||||
StompBrokerRelayMessageHandler handler = new StompBrokerRelayMessageHandler(
|
||||
getClientInboundChannel(), getClientOutboundChannel(),
|
||||
brokerChannel, getDestinationPrefixes());
|
||||
|
||||
handler.setRelayHost(this.relayHost);
|
||||
handler.setRelayPort(this.relayPort);
|
||||
|
||||
@@ -33,8 +33,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A default implementation of {@code UserDestinationResolver} that relies
|
||||
* on a {@link org.springframework.messaging.simp.user.UserSessionRegistry} to
|
||||
* find active sessions for a user.
|
||||
* on a {@link SimpUserRegistry} to find active sessions for a user.
|
||||
*
|
||||
* <p>When a user attempts to subscribe, e.g. to "/user/queue/position-updates",
|
||||
* the "/user" prefix is removed and a unique suffix added based on the session
|
||||
@@ -54,7 +53,7 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
|
||||
private static final Log logger = LogFactory.getLog(DefaultUserDestinationResolver.class);
|
||||
|
||||
|
||||
private final UserSessionRegistry sessionRegistry;
|
||||
private final SimpUserRegistry userRegistry;
|
||||
|
||||
private String prefix = "/user/";
|
||||
|
||||
@@ -62,19 +61,19 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
|
||||
/**
|
||||
* Create an instance that will access user session id information through
|
||||
* the provided registry.
|
||||
* @param sessionRegistry the registry, never {@code null}
|
||||
* @param userRegistry the registry, never {@code null}
|
||||
*/
|
||||
public DefaultUserDestinationResolver(UserSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "'sessionRegistry' must not be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
public DefaultUserDestinationResolver(SimpUserRegistry userRegistry) {
|
||||
Assert.notNull(userRegistry, "'userRegistry' must not be null");
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured {@link UserSessionRegistry}.
|
||||
* Return the configured {@link SimpUserRegistry}.
|
||||
*/
|
||||
public UserSessionRegistry getUserSessionRegistry() {
|
||||
return this.sessionRegistry;
|
||||
public SimpUserRegistry getSimpUserRegistry() {
|
||||
return this.userRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,20 +140,32 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
|
||||
Assert.isTrue(userEnd > 0, "Expected destination pattern \"/user/{userId}/**\"");
|
||||
String actualDestination = destination.substring(userEnd);
|
||||
String subscribeDestination = this.prefix.substring(0, prefixEnd - 1) + actualDestination;
|
||||
String user = destination.substring(prefixEnd, userEnd);
|
||||
user = StringUtils.replace(user, "%2F", "/");
|
||||
String userName = destination.substring(prefixEnd, userEnd);
|
||||
userName = StringUtils.replace(userName, "%2F", "/");
|
||||
Set<String> sessionIds;
|
||||
if (user.equals(sessionId)) {
|
||||
user = null;
|
||||
sessionIds = Collections.singleton(sessionId);
|
||||
}
|
||||
else if (this.sessionRegistry.getSessionIds(user).contains(sessionId)) {
|
||||
if (userName.equals(sessionId)) {
|
||||
userName = null;
|
||||
sessionIds = Collections.singleton(sessionId);
|
||||
}
|
||||
else {
|
||||
sessionIds = this.sessionRegistry.getSessionIds(user);
|
||||
SimpUser user = this.userRegistry.getUser(userName);
|
||||
if (user != null) {
|
||||
if (user.getSession(sessionId) != null) {
|
||||
sessionIds = Collections.singleton(sessionId);
|
||||
}
|
||||
else {
|
||||
Set<SimpSession> sessions = user.getSessions();
|
||||
sessionIds = new HashSet<String>(sessions.size());
|
||||
for (SimpSession session : sessions) {
|
||||
sessionIds.add(session.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sessionIds = Collections.<String>emptySet();
|
||||
}
|
||||
}
|
||||
return new ParseResult(actualDestination, subscribeDestination, sessionIds, user);
|
||||
return new ParseResult(actualDestination, subscribeDestination, sessionIds, userName);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -174,6 +185,7 @@ public class DefaultUserDestinationResolver implements UserDestinationResolver {
|
||||
* @param user the target user, possibly {@code null}, e.g if not authenticated.
|
||||
* @return a target destination, or {@code null} if none
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected String getTargetDestination(String sourceDestination, String actualDestination,
|
||||
String sessionId, String user) {
|
||||
|
||||
|
||||
@@ -29,7 +29,11 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
* @deprecated as of 4.2 this class is no longer used, see deprecation notes
|
||||
* on {@link UserSessionRegistry} for more details.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class DefaultUserSessionRegistry implements UserSessionRegistry {
|
||||
|
||||
// userId -> sessionId
|
||||
@@ -72,4 +76,4 @@ public class DefaultUserSessionRegistry implements UserSessionRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A user registry that is a composite of the "local" user registry as well as
|
||||
* snapshots of remote user registries. For use with
|
||||
* {@link UserRegistryMessageHandler} which broadcasts periodically the content
|
||||
* of the local registry and receives updates from other servers.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MultiServerUserRegistry implements SimpUserRegistry, SmartApplicationListener {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final SimpUserRegistry localRegistry;
|
||||
|
||||
private final SmartApplicationListener listener;
|
||||
|
||||
private final Map<String, UserRegistryDto> remoteRegistries =
|
||||
new ConcurrentHashMap<String, UserRegistryDto>();
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance wrapping the local user registry.
|
||||
*/
|
||||
public MultiServerUserRegistry(SimpUserRegistry localRegistry) {
|
||||
Assert.notNull(localRegistry, "'localRegistry' is required.");
|
||||
this.localRegistry = localRegistry;
|
||||
this.listener = (this.localRegistry instanceof SmartApplicationListener ?
|
||||
(SmartApplicationListener) this.localRegistry : new NoOpSmartApplicationListener());
|
||||
this.id = generateId();
|
||||
}
|
||||
|
||||
private static String generateId() {
|
||||
String host;
|
||||
try {
|
||||
host = InetAddress.getLocalHost().getHostAddress();
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
host = "unknown";
|
||||
}
|
||||
return host + "-" + UUID.randomUUID();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SimpUser getUser(String userName) {
|
||||
SimpUser user = this.localRegistry.getUser(userName);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
for (UserRegistryDto registry : this.remoteRegistries.values()) {
|
||||
user = registry.getUsers().get(userName);
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpUser> getUsers() {
|
||||
Set<SimpUser> result = new HashSet<SimpUser>(this.localRegistry.getUsers());
|
||||
for (UserRegistryDto registry : this.remoteRegistries.values()) {
|
||||
result.addAll(registry.getUsers().values());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
|
||||
Set<SimpSubscription> result = new HashSet<SimpSubscription>(this.localRegistry.findSubscriptions(matcher));
|
||||
for (UserRegistryDto registry : this.remoteRegistries.values()) {
|
||||
result.addAll(registry.findSubscriptions(matcher));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
|
||||
return this.listener.supportsEventType(eventType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSourceType(Class<?> sourceType) {
|
||||
return this.listener.supportsSourceType(sourceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
this.listener.onApplicationEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.listener.getOrder();
|
||||
}
|
||||
|
||||
Object getLocalRegistryDto() {
|
||||
return new UserRegistryDto(this.id, this.localRegistry);
|
||||
}
|
||||
|
||||
void addRemoteRegistryDto(Message<?> message, MessageConverter converter, long expirationPeriod) {
|
||||
UserRegistryDto registryDto = (UserRegistryDto) converter.fromMessage(message, UserRegistryDto.class);
|
||||
if (registryDto != null && !registryDto.getId().equals(this.id)) {
|
||||
long expirationTime = System.currentTimeMillis() + expirationPeriod;
|
||||
registryDto.setExpirationTime(expirationTime);
|
||||
registryDto.restoreParentReferences();
|
||||
this.remoteRegistries.put(registryDto.getId(), registryDto);
|
||||
}
|
||||
}
|
||||
|
||||
void purgeExpiredRegistries() {
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<String, UserRegistryDto>> iterator = this.remoteRegistries.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, UserRegistryDto> entry = iterator.next();
|
||||
if (now > entry.getValue().getExpirationTime()) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "local=[" + this.localRegistry + "], remote=" + this.remoteRegistries + "]";
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class UserRegistryDto {
|
||||
|
||||
private String id;
|
||||
|
||||
private Map<String, SimpUserDto> users;
|
||||
|
||||
private long expirationTime;
|
||||
|
||||
|
||||
public UserRegistryDto() {
|
||||
}
|
||||
|
||||
public UserRegistryDto(String id, SimpUserRegistry registry) {
|
||||
this.id = id;
|
||||
Set<SimpUser> users = registry.getUsers();
|
||||
this.users = new HashMap<String, SimpUserDto>(users.size());
|
||||
for (SimpUser user : users) {
|
||||
this.users.put(user.getName(), new SimpUserDto(user));
|
||||
}
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setUsers(Map<String, SimpUserDto> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public Map<String, SimpUserDto> getUsers() {
|
||||
return this.users;
|
||||
}
|
||||
|
||||
public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
|
||||
Set<SimpSubscription> result = new HashSet<SimpSubscription>();
|
||||
for (SimpUserDto user : this.users.values()) {
|
||||
for (SimpSessionDto session : user.sessions) {
|
||||
for (SimpSubscription subscription : session.subscriptions) {
|
||||
if (matcher.match(subscription)) {
|
||||
result.add(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setExpirationTime(long expirationTime) {
|
||||
this.expirationTime = expirationTime;
|
||||
}
|
||||
|
||||
public long getExpirationTime() {
|
||||
return this.expirationTime;
|
||||
}
|
||||
|
||||
private void restoreParentReferences() {
|
||||
for (SimpUserDto user : this.users.values()) {
|
||||
user.restoreParentReferences();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "id=" + this.id + ", users=" + this.users;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SimpUserDto implements SimpUser {
|
||||
|
||||
private String name;
|
||||
|
||||
private Set<SimpSessionDto> sessions;
|
||||
|
||||
|
||||
public SimpUserDto() {
|
||||
this.sessions = new HashSet<SimpSessionDto>(1);
|
||||
}
|
||||
|
||||
public SimpUserDto(SimpUser user) {
|
||||
this.name = user.getName();
|
||||
Set<SimpSession> sessions = user.getSessions();
|
||||
this.sessions = new HashSet<SimpSessionDto>(sessions.size());
|
||||
for (SimpSession session : sessions) {
|
||||
this.sessions.add(new SimpSessionDto(session));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSessions() {
|
||||
return !this.sessions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpSession> getSessions() {
|
||||
return new HashSet<SimpSession>(this.sessions);
|
||||
}
|
||||
|
||||
public void setSessions(Set<SimpSessionDto> sessions) {
|
||||
this.sessions.addAll(sessions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpSessionDto getSession(String sessionId) {
|
||||
for (SimpSessionDto session : this.sessions) {
|
||||
if (session.getId().equals(sessionId)) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void restoreParentReferences() {
|
||||
for (SimpSessionDto session : this.sessions) {
|
||||
session.setUser(this);
|
||||
session.restoreParentReferences();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || !(other instanceof SimpUser)) {
|
||||
return false;
|
||||
}
|
||||
return this.name.equals(((SimpUser) other).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name=" + this.name + ", sessions=" + this.sessions;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SimpSessionDto implements SimpSession {
|
||||
|
||||
private String id;
|
||||
|
||||
private SimpUserDto user;
|
||||
|
||||
private Set<SimpSubscriptionDto> subscriptions;
|
||||
|
||||
|
||||
public SimpSessionDto() {
|
||||
this.subscriptions = new HashSet<SimpSubscriptionDto>(4);
|
||||
}
|
||||
|
||||
public SimpSessionDto(SimpSession session) {
|
||||
this.id = session.getId();
|
||||
Set<SimpSubscription> subscriptions = session.getSubscriptions();
|
||||
this.subscriptions = new HashSet<SimpSubscriptionDto>(subscriptions.size());
|
||||
for (SimpSubscription subscription : subscriptions) {
|
||||
this.subscriptions.add(new SimpSubscriptionDto(subscription));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpUserDto getUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(SimpUserDto user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpSubscription> getSubscriptions() {
|
||||
return new HashSet<SimpSubscription>(this.subscriptions);
|
||||
}
|
||||
|
||||
public void setSubscriptions(Set<SimpSubscriptionDto> subscriptions) {
|
||||
this.subscriptions.addAll(subscriptions);
|
||||
}
|
||||
|
||||
private void restoreParentReferences() {
|
||||
for (SimpSubscriptionDto subscription : this.subscriptions) {
|
||||
subscription.setSession(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || !(other instanceof SimpSession)) {
|
||||
return false;
|
||||
}
|
||||
return this.id.equals(((SimpSession) other).getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "id=" + this.id + ", subscriptions=" + this.subscriptions;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SimpSubscriptionDto implements SimpSubscription {
|
||||
|
||||
private String id;
|
||||
|
||||
private SimpSessionDto session;
|
||||
|
||||
private String destination;
|
||||
|
||||
|
||||
public SimpSubscriptionDto() {
|
||||
}
|
||||
|
||||
public SimpSubscriptionDto(SimpSubscription subscription) {
|
||||
this.id = subscription.getId();
|
||||
this.destination = subscription.getDestination();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpSessionDto getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public void setSession(SimpSessionDto session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * this.id.hashCode() + ObjectUtils.nullSafeHashCode(getSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || !(other instanceof SimpSubscription)) {
|
||||
return false;
|
||||
}
|
||||
SimpSubscription otherSubscription = (SimpSubscription) other;
|
||||
return (ObjectUtils.nullSafeEquals(getSession(), otherSubscription.getSession()) &&
|
||||
this.id.equals(otherSubscription.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "destination=" + this.destination;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoOpSmartApplicationListener implements SmartApplicationListener {
|
||||
|
||||
@Override
|
||||
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSourceType(Class<?> sourceType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a session of connected user.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface SimpSession {
|
||||
|
||||
/**
|
||||
* Return the session id.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Return the user associated with the session.
|
||||
*/
|
||||
SimpUser getUser();
|
||||
|
||||
/**
|
||||
* Return the subscriptions for this session.
|
||||
*/
|
||||
Set<SimpSubscription> getSubscriptions();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
/**
|
||||
* Represents a subscription within a user session.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface SimpSubscription {
|
||||
|
||||
/**
|
||||
* Return the id associated of the subscription, never {@code null}.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Return the session of the subscription, never {@code null}.
|
||||
*/
|
||||
SimpSession getSession();
|
||||
|
||||
/**
|
||||
* Return the subscription's destination, never {@code null}.
|
||||
*/
|
||||
String getDestination();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
/**
|
||||
* A strategy for matching subscriptions.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface SimpSubscriptionMatcher {
|
||||
|
||||
/**
|
||||
* Match the given subscription.
|
||||
* @param subscription the subscription to match
|
||||
* @return {@code true} in case of match, {@code false} otherwise.
|
||||
*/
|
||||
boolean match(SimpSubscription subscription);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a connected user.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface SimpUser {
|
||||
|
||||
/**
|
||||
* The unique user name.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Whether the user has any sessions.
|
||||
*/
|
||||
boolean hasSessions();
|
||||
|
||||
/**
|
||||
* Look up the session for the given id.
|
||||
* @param sessionId the session id
|
||||
* @return the matching session of {@code null}.
|
||||
*/
|
||||
SimpSession getSession(String sessionId);
|
||||
|
||||
/**
|
||||
* Return the sessions for the user.
|
||||
* The returned set is a copy and will never be modified.
|
||||
* @return a set of session ids, or an empty set.
|
||||
*/
|
||||
Set<SimpSession> getSessions();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A registry of currently connected users.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface SimpUserRegistry {
|
||||
|
||||
/**
|
||||
* Get the user for the given name.
|
||||
* @param userName the name of the user to look up
|
||||
* @return the user or {@code null} if not connected
|
||||
*/
|
||||
SimpUser getUser(String userName);
|
||||
|
||||
/**
|
||||
* Return a snapshot of all connected users. The returned set is a copy and
|
||||
* will never be modified.
|
||||
* @return the connected users or an empty set.
|
||||
*/
|
||||
Set<SimpUser> getUsers();
|
||||
|
||||
/**
|
||||
* Find subscriptions with the given matcher.
|
||||
* @param matcher the matcher to use
|
||||
* @return a set of matching subscriptions or an empty set.
|
||||
*/
|
||||
Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher);
|
||||
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
|
||||
* <p>By default this is not set.
|
||||
* @param destination the target destination.
|
||||
*/
|
||||
public void setUserDestinationBroadcast(String destination) {
|
||||
public void setBroadcastDestination(String destination) {
|
||||
this.broadcastHandler = (StringUtils.hasText(destination) ?
|
||||
new BroadcastHandler(this.messagingTemplate, destination) : null);
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public class UserDestinationMessageHandler implements MessageHandler, SmartLifec
|
||||
/**
|
||||
* Return the configured destination for unresolved messages.
|
||||
*/
|
||||
public String getUserDestinationBroadcast() {
|
||||
public String getBroadcastDestination() {
|
||||
return (this.broadcastHandler != null ? this.broadcastHandler.getBroadcastDestination() : null);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ public class UserDestinationResult {
|
||||
* @return the user name or {@code null} if we have a session id only such as
|
||||
* when the user is not authenticated; in such cases it is possible to use
|
||||
* sessionId in place of a user name thus removing the need for a user-to-session
|
||||
* lookup via {@link org.springframework.messaging.simp.user.UserSessionRegistry}.
|
||||
* lookup via {@link SimpUserRegistry}.
|
||||
*/
|
||||
public String getUser() {
|
||||
return this.user;
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.messaging.simp.broker.BrokerAvailabilityEvent;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A MessageHandler that is subscribed to listen to broadcasts of user registry
|
||||
* information from other application servers as well as to periodically
|
||||
* broadcast the content of the local user registry. The aggregated information
|
||||
* is maintained in a {@link MultiServerUserRegistry}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public class UserRegistryMessageHandler implements MessageHandler, ApplicationListener<BrokerAvailabilityEvent> {
|
||||
|
||||
private final MultiServerUserRegistry userRegistry;
|
||||
|
||||
private final SimpMessagingTemplate brokerTemplate;
|
||||
|
||||
private final String broadcastDestination;
|
||||
|
||||
private final TaskScheduler scheduler;
|
||||
|
||||
private final UserRegistryTask schedulerTask = new UserRegistryTask();
|
||||
|
||||
private volatile ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
private long registryExpirationPeriod = 20 * 1000;
|
||||
|
||||
|
||||
public UserRegistryMessageHandler(SimpUserRegistry userRegistry, SimpMessagingTemplate brokerTemplate,
|
||||
String broadcastDestination, TaskScheduler scheduler) {
|
||||
|
||||
Assert.notNull(userRegistry, "'userRegistry' is required");
|
||||
Assert.isInstanceOf(MultiServerUserRegistry.class, userRegistry);
|
||||
Assert.notNull(brokerTemplate, "'brokerTemplate' is required");
|
||||
Assert.hasText(broadcastDestination, "'broadcastDestination' is required");
|
||||
Assert.notNull(scheduler, "'scheduler' is required");
|
||||
|
||||
|
||||
this.userRegistry = (MultiServerUserRegistry) userRegistry;
|
||||
this.brokerTemplate = brokerTemplate;
|
||||
this.broadcastDestination = broadcastDestination;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the destination for broadcasting user registry information to.
|
||||
*/
|
||||
public String getBroadcastDestination() {
|
||||
return this.broadcastDestination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure how long before a remote registry snapshot expires.
|
||||
* <p>By default this is set to 20000 (20 seconds).
|
||||
* @param expirationPeriod the expiration period in milliseconds
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setRegistryExpirationPeriod(long expirationPeriod) {
|
||||
this.registryExpirationPeriod = expirationPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured registry expiration period.
|
||||
*/
|
||||
public long getRegistryExpirationPeriod() {
|
||||
return this.registryExpirationPeriod;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(BrokerAvailabilityEvent event) {
|
||||
if (event.isBrokerAvailable()) {
|
||||
long delay = getRegistryExpirationPeriod() / 2;
|
||||
this.scheduledFuture = this.scheduler.scheduleWithFixedDelay(this.schedulerTask, delay);
|
||||
}
|
||||
else if (this.scheduledFuture != null ){
|
||||
this.scheduledFuture.cancel(true);
|
||||
this.scheduledFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
MessageConverter converter = this.brokerTemplate.getMessageConverter();
|
||||
this.userRegistry.addRemoteRegistryDto(message, converter, getRegistryExpirationPeriod());
|
||||
}
|
||||
|
||||
|
||||
private class UserRegistryTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
|
||||
accessor.setHeader(SimpMessageHeaderAccessor.IGNORE_ERROR, true);
|
||||
accessor.setLeaveMutable(true);
|
||||
Object payload = userRegistry.getLocalRegistryDto();
|
||||
brokerTemplate.convertAndSend(getBroadcastDestination(), payload, accessor.getMessageHeaders());
|
||||
}
|
||||
finally {
|
||||
userRegistry.purgeExpiredRegistries();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,34 +19,41 @@ package org.springframework.messaging.simp.user;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A registry for looking up active user sessions. For use when resolving user
|
||||
* destinations.
|
||||
* A contract for adding and removing user sessions.
|
||||
*
|
||||
* <p>As of 4.2 this interface extends {@link SimpUserRegistry}.
|
||||
* exposing methods to return all registered users as well as to provide more
|
||||
* extensive information for each user.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
* @see DefaultUserDestinationResolver
|
||||
* @deprecated in favor of {@link SimpUserRegistry} in combination with
|
||||
* {@link org.springframework.context.ApplicationListener} listening for
|
||||
* {@link org.springframework.web.socket.messaging.AbstractSubProtocolEvent} events.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface UserSessionRegistry {
|
||||
|
||||
/**
|
||||
* Return the active session id's for the user.
|
||||
* @param user the user
|
||||
* @return a set with 0 or more session id's, never {@code null}.
|
||||
* Return the active session ids for the user.
|
||||
* The returned set is a snapshot that will never be modified.
|
||||
* @param userName the user to look up
|
||||
* @return a set with 0 or more session ids, never {@code null}.
|
||||
*/
|
||||
Set<String> getSessionIds(String user);
|
||||
Set<String> getSessionIds(String userName);
|
||||
|
||||
/**
|
||||
* Register an active session id for a user.
|
||||
* @param user the user
|
||||
* @param userName the user name
|
||||
* @param sessionId the session id
|
||||
*/
|
||||
void registerSessionId(String user, String sessionId);
|
||||
void registerSessionId(String userName, String sessionId);
|
||||
|
||||
/**
|
||||
* Unregister an active session id for a user.
|
||||
* @param user the user
|
||||
* @param userName the user name
|
||||
* @param sessionId the session id
|
||||
*/
|
||||
void unregisterSessionId(String user, String sessionId);
|
||||
void unregisterSessionId(String userName, String sessionId);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.messaging.simp.user;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* A temporary adapter to allow use of deprecated {@link UserSessionRegistry}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class UserSessionRegistryAdapter implements SimpUserRegistry {
|
||||
|
||||
private final UserSessionRegistry delegate;
|
||||
|
||||
|
||||
public UserSessionRegistryAdapter(UserSessionRegistry delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SimpUser getUser(String userName) {
|
||||
Set<String> sessionIds = this.delegate.getSessionIds(userName);
|
||||
return (!CollectionUtils.isEmpty(sessionIds) ? new SimpleSimpUser(userName, sessionIds) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpUser> getUsers() {
|
||||
throw new UnsupportedOperationException("UserSessionRegistry does not expose a listing of users.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpSubscription> findSubscriptions(SimpSubscriptionMatcher matcher) {
|
||||
throw new UnsupportedOperationException("UserSessionRegistry does not support operations across users.");
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleSimpUser implements SimpUser {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Map<String, SimpSession> sessions;
|
||||
|
||||
|
||||
public SimpleSimpUser(String name, Set<String> sessionIds) {
|
||||
this.name = name;
|
||||
this.sessions = new HashMap<String, SimpSession>(sessionIds.size());
|
||||
for (String sessionId : sessionIds) {
|
||||
this.sessions.put(sessionId, new SimpleSimpSession(sessionId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSessions() {
|
||||
return !this.sessions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpSession getSession(String sessionId) {
|
||||
return this.sessions.get(sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpSession> getSessions() {
|
||||
return new HashSet<SimpSession>(this.sessions.values());
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimpleSimpSession implements SimpSession {
|
||||
|
||||
private final String id;
|
||||
|
||||
|
||||
public SimpleSimpSession(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpUser getUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SimpSubscription> getSubscriptions() {
|
||||
return Collections.<SimpSubscription>emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* unique to a user's sessions), primarily translating the destinations and then
|
||||
* forwarding the updated message to the broker.
|
||||
*
|
||||
* <p>Also included is {@link org.springframework.messaging.simp.user.UserSessionRegistry}
|
||||
* <p>Also included is {@link org.springframework.messaging.simp.user.SimpUserRegistry}
|
||||
* for keeping track of connected user sessions.
|
||||
*/
|
||||
package org.springframework.messaging.simp.user;
|
||||
|
||||
Reference in New Issue
Block a user