-
Notifications
You must be signed in to change notification settings - Fork 37.7k
/
DefaultUserDestinationResolver.java
242 lines (202 loc) · 8.12 KB
/
DefaultUserDestinationResolver.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*
* Copyright 2002-2014 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* A default implementation of {@link UserDestinationResolver} that relies
* on the {@link org.springframework.messaging.simp.user.UserSessionRegistry}
* provided to the constructor to find the sessionIds associated with a user
* and then uses the sessionId to make the target destination unique.
* <p>
* When a user attempts to subscribe to "/user/queue/position-updates", the
* "/user" prefix is removed and a unique suffix added, resulting in something
* like "/queue/position-updates-useri9oqdfzo" where the suffix is based on the
* user's session and ensures it does not collide with any other users attempting
* to subscribe to "/user/queue/position-updates".
* <p>
* When a message is sent to a user with a destination such as
* "/user/{username}/queue/position-updates", the "/user/{username}" prefix is
* removed and the suffix added, resulting in something like
* "/queue/position-updates-useri9oqdfzo".
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class DefaultUserDestinationResolver implements UserDestinationResolver {
private static final Log logger = LogFactory.getLog(DefaultUserDestinationResolver.class);
private final UserSessionRegistry userSessionRegistry;
private String destinationPrefix = "/user/";
/**
* Create an instance that will access user session id information through
* the provided registry.
* @param userSessionRegistry the registry, never {@code null}
*/
public DefaultUserDestinationResolver(UserSessionRegistry userSessionRegistry) {
Assert.notNull(userSessionRegistry, "'userSessionRegistry' must not be null");
this.userSessionRegistry = userSessionRegistry;
}
/**
* The prefix used to identify user destinations. Any destinations that do not
* start with the given prefix are not be resolved.
* <p>The default value is "/user/".
* @param prefix the prefix to use
*/
public void setUserDestinationPrefix(String prefix) {
Assert.hasText(prefix, "prefix must not be empty");
this.destinationPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
}
/**
* Return the prefix used to identify user destinations. Any destinations that do not
* start with the given prefix are not be resolved.
* <p>By default "/user/queue/".
*/
public String getDestinationPrefix() {
return this.destinationPrefix;
}
/**
* Return the configured {@link UserSessionRegistry}.
*/
public UserSessionRegistry getUserSessionRegistry() {
return this.userSessionRegistry;
}
@Override
public UserDestinationResult resolveDestination(Message<?> message) {
String destination = SimpMessageHeaderAccessor.getDestination(message.getHeaders());
DestinationInfo info = parseUserDestination(message);
if (info == null) {
return null;
}
Set<String> targetDestinations = new HashSet<String>();
for (String sessionId : info.getSessionIds()) {
targetDestinations.add(getTargetDestination(destination,
info.getDestinationWithoutPrefix(), sessionId, info.getUser()));
}
return new UserDestinationResult(destination,
targetDestinations, info.getSubscribeDestination(), info.getUser());
}
private DestinationInfo parseUserDestination(Message<?> message) {
MessageHeaders headers = message.getHeaders();
SimpMessageType messageType = SimpMessageHeaderAccessor.getMessageType(headers);
String destination = SimpMessageHeaderAccessor.getDestination(headers);
Principal principal = SimpMessageHeaderAccessor.getUser(headers);
String sessionId = SimpMessageHeaderAccessor.getSessionId(headers);
String destinationWithoutPrefix;
String subscribeDestination;
String user;
Set<String> sessionIds;
if (SimpMessageType.SUBSCRIBE.equals(messageType) || SimpMessageType.UNSUBSCRIBE.equals(messageType)) {
if (!checkDestination(destination, this.destinationPrefix)) {
return null;
}
if (principal == null) {
logger.error("Ignoring message, no principal info available");
return null;
}
if (sessionId == null) {
logger.error("Ignoring message, no session id available");
return null;
}
destinationWithoutPrefix = destination.substring(this.destinationPrefix.length()-1);
subscribeDestination = destination;
user = principal.getName();
sessionIds = Collections.singleton(sessionId);
}
else if (SimpMessageType.MESSAGE.equals(messageType)) {
if (!checkDestination(destination, this.destinationPrefix)) {
return null;
}
int startIndex = this.destinationPrefix.length();
int endIndex = destination.indexOf('/', startIndex);
Assert.isTrue(endIndex > 0, "Expected destination pattern \"/principal/{userId}/**\"");
destinationWithoutPrefix = destination.substring(endIndex);
subscribeDestination = this.destinationPrefix.substring(0, startIndex-1) + destinationWithoutPrefix;
user = destination.substring(startIndex, endIndex);
user = StringUtils.replace(user, "%2F", "/");
sessionIds = (sessionId != null ?
Collections.singleton(sessionId) : this.userSessionRegistry.getSessionIds(user));
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring " + messageType + " message");
}
return null;
}
return new DestinationInfo(destinationWithoutPrefix, subscribeDestination, user, sessionIds);
}
protected boolean checkDestination(String destination, String requiredPrefix) {
if (destination == null) {
logger.trace("Ignoring message, no destination");
return false;
}
if (!destination.startsWith(requiredPrefix)) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring message to " + destination + ", not a \"user\" destination");
}
return false;
}
return true;
}
/**
* Return the target destination to use. Provided as input are the original source
* destination, as well as the same destination with the target prefix removed.
*
* @param sourceDestination the source destination from the input message
* @param sourceDestinationWithoutPrefix the source destination with the target prefix removed
* @param sessionId an active user session id
* @param user the user
* @return the target destination
*/
protected String getTargetDestination(String sourceDestination,
String sourceDestinationWithoutPrefix, String sessionId, String user) {
return sourceDestinationWithoutPrefix + "-user" + sessionId;
}
private static class DestinationInfo {
private final String destinationWithoutPrefix;
private final String subscribeDestination;
private final String user;
private final Set<String> sessionIds;
private DestinationInfo(String destinationWithoutPrefix, String subscribeDestination, String user,
Set<String> sessionIds) {
this.user = user;
this.destinationWithoutPrefix = destinationWithoutPrefix;
this.subscribeDestination = subscribeDestination;
this.sessionIds = sessionIds;
}
public String getDestinationWithoutPrefix() {
return this.destinationWithoutPrefix;
}
public String getSubscribeDestination() {
return this.subscribeDestination;
}
public String getUser() {
return this.user;
}
public Set<String> getSessionIds() {
return this.sessionIds;
}
}
}