All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.bitsensor.plugins.shaded.org.springframework.messaging.simp.user.DefaultUserDestinationResolver Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2017 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 io.bitsensor.plugins.shaded.org.springframework.messaging.simp.user;

import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import io.bitsensor.plugins.shaded.org.apache.commons.logging.Log;
import io.bitsensor.plugins.shaded.org.apache.commons.logging.LogFactory;

import io.bitsensor.plugins.shaded.org.springframework.messaging.Message;
import io.bitsensor.plugins.shaded.org.springframework.messaging.MessageHeaders;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageType;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.PathMatcher;
import io.bitsensor.plugins.shaded.org.springframework.util.StringUtils;

/**
 * A default implementation of {@code UserDestinationResolver} that relies
 * on a {@link SimpUserRegistry} to find active sessions for a user.
 *
 * 

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 * id, e.g. "/queue/position-updates-useri9oqdfzo" to ensure different users can * subscribe to the same logical destination without colliding. * *

When sending to a user, e.g. "/user/{username}/queue/position-updates", the * "/user/{username}" prefix is removed and a suffix based on active session id's * is added, e.g. "/queue/position-updates-useri9oqdfzo". * * @author Rossen Stoyanchev * @author Brian Clozel * @since 4.0 */ public class DefaultUserDestinationResolver implements UserDestinationResolver { private static final Log logger = LogFactory.getLog(DefaultUserDestinationResolver.class); private final SimpUserRegistry userRegistry; private String prefix = "/user/"; private boolean keepLeadingSlash = true; /** * Create an instance that will access user session id information through * the provided registry. * @param userRegistry the registry, never {@code null} */ public DefaultUserDestinationResolver(SimpUserRegistry userRegistry) { Assert.notNull(userRegistry, "'userRegistry' must not be null"); this.userRegistry = userRegistry; } /** * Return the configured {@link SimpUserRegistry}. */ public SimpUserRegistry getSimpUserRegistry() { return this.userRegistry; } /** * The prefix used to identify user destinations. Any destinations that do not * start with the given prefix are not be resolved. *

The default prefix is "/user/". * @param prefix the prefix to use */ public void setUserDestinationPrefix(String prefix) { Assert.hasText(prefix, "prefix must not be empty"); this.prefix = prefix.endsWith("/") ? prefix : prefix + "/"; } /** * Return the configured prefix for user destinations. */ public String getDestinationPrefix() { return this.prefix; } /** * Provide the {@code PathMatcher} in use for working with destinations * which in turn helps to determine whether the leading slash should be * kept in actual destinations after removing the * {@link #setUserDestinationPrefix userDestinationPrefix}. *

By default actual destinations have a leading slash, e.g. * {@code /queue/position-updates} which makes sense with brokers that * support destinations with slash as separator. When a {@code PathMatcher} * is provided that supports an alternative separator, then resulting * destinations won't have a leading slash, e.g. {@code * jms.queue.position-updates}. * @param pathMatcher the PathMatcher used to work with destinations * @since 4.3 */ public void setPathMatcher(PathMatcher pathMatcher) { if (pathMatcher != null) { this.keepLeadingSlash = pathMatcher.combine("1", "2").equals("1/2"); } } @Override public UserDestinationResult resolveDestination(Message message) { ParseResult parseResult = parse(message); if (parseResult == null) { return null; } String user = parseResult.getUser(); String sourceDestination = parseResult.getSourceDestination(); Set targetSet = new HashSet(); for (String sessionId : parseResult.getSessionIds()) { String actualDestination = parseResult.getActualDestination(); String targetDestination = getTargetDestination( sourceDestination, actualDestination, sessionId, user); if (targetDestination != null) { targetSet.add(targetDestination); } } String subscribeDestination = parseResult.getSubscribeDestination(); return new UserDestinationResult(sourceDestination, targetSet, subscribeDestination, user); } private ParseResult parse(Message message) { MessageHeaders headers = message.getHeaders(); String sourceDestination = SimpMessageHeaderAccessor.getDestination(headers); if (sourceDestination == null || !checkDestination(sourceDestination, this.prefix)) { return null; } SimpMessageType messageType = SimpMessageHeaderAccessor.getMessageType(headers); switch (messageType) { case SUBSCRIBE: case UNSUBSCRIBE: return parseSubscriptionMessage(message, sourceDestination); case MESSAGE: return parseMessage(headers, sourceDestination); default: return null; } } private ParseResult parseSubscriptionMessage(Message message, String sourceDestination) { MessageHeaders headers = message.getHeaders(); String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); if (sessionId == null) { logger.error("No session id. Ignoring " + message); return null; } int prefixEnd = this.prefix.length() - 1; String actualDestination = sourceDestination.substring(prefixEnd); if (!this.keepLeadingSlash) { actualDestination = actualDestination.substring(1); } Principal principal = SimpMessageHeaderAccessor.getUser(headers); String user = (principal != null ? principal.getName() : null); Set sessionIds = Collections.singleton(sessionId); return new ParseResult(sourceDestination, actualDestination, sourceDestination, sessionIds, user); } private ParseResult parseMessage(MessageHeaders headers, String sourceDestination) { int prefixEnd = this.prefix.length(); int userEnd = sourceDestination.indexOf('/', prefixEnd); Assert.isTrue(userEnd > 0, "Expected destination pattern \"/user/{userId}/**\""); String actualDestination = sourceDestination.substring(userEnd); String subscribeDestination = this.prefix.substring(0, prefixEnd - 1) + actualDestination; String userName = sourceDestination.substring(prefixEnd, userEnd); userName = StringUtils.replace(userName, "%2F", "/"); String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); Set sessionIds; if (userName.equals(sessionId)) { userName = null; sessionIds = Collections.singleton(sessionId); } else { sessionIds = getSessionIdsByUser(userName, sessionId); } if (!this.keepLeadingSlash) { actualDestination = actualDestination.substring(1); } return new ParseResult(sourceDestination, actualDestination, subscribeDestination, sessionIds, userName); } private Set getSessionIdsByUser(String userName, String sessionId) { Set sessionIds; SimpUser user = this.userRegistry.getUser(userName); if (user != null) { if (user.getSession(sessionId) != null) { sessionIds = Collections.singleton(sessionId); } else { Set sessions = user.getSessions(); sessionIds = new HashSet(sessions.size()); for (SimpSession session : sessions) { sessionIds.add(session.getId()); } } } else { sessionIds = Collections.emptySet(); } return sessionIds; } protected boolean checkDestination(String destination, String requiredPrefix) { return destination.startsWith(requiredPrefix); } /** * This method determines how to translate the source "user" destination to an * actual target destination for the given active user session. * @param sourceDestination the source destination from the input message. * @param actualDestination a subset of the destination without any user prefix. * @param sessionId the id of an active user session, never {@code null}. * @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) { return actualDestination + "-user" + sessionId; } @Override public String toString() { return "DefaultUserDestinationResolver[prefix=" + this.prefix + "]"; } /** * A temporary placeholder for a parsed source "user" destination. */ private static class ParseResult { private final String sourceDestination; private final String actualDestination; private final String subscribeDestination; private final Set sessionIds; private final String user; public ParseResult(String sourceDest, String actualDest, String subscribeDest, Set sessionIds, String user) { this.sourceDestination = sourceDest; this.actualDestination = actualDest; this.subscribeDestination = subscribeDest; this.sessionIds = sessionIds; this.user = user; } public String getSourceDestination() { return this.sourceDestination; } public String getActualDestination() { return this.actualDestination; } public String getSubscribeDestination() { return this.subscribeDestination; } public Set getSessionIds() { return this.sessionIds; } public String getUser() { return this.user; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy