16
16
17
17
package org .springframework .messaging .simp .broker ;
18
18
19
- import java .util .*;
19
+ import java .util .Collection ;
20
+ import java .util .HashSet ;
21
+ import java .util .LinkedHashMap ;
22
+ import java .util .List ;
23
+ import java .util .Map ;
24
+ import java .util .Set ;
20
25
import java .util .concurrent .ConcurrentHashMap ;
21
26
import java .util .concurrent .ConcurrentMap ;
22
27
25
30
import org .springframework .util .Assert ;
26
31
import org .springframework .util .LinkedMultiValueMap ;
27
32
import org .springframework .util .MultiValueMap ;
33
+ import org .springframework .util .PathMatcher ;
28
34
29
35
/**
30
36
* A default, simple in-memory implementation of {@link SubscriptionRegistry}.
35
41
*/
36
42
public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry {
37
43
44
+ /** Default maximum number of entries for the destination cache: 1024 */
45
+ public static final int DEFAULT_CACHE_LIMIT = 1024 ;
46
+
47
+
48
+ /** The maximum number of entries in the cache */
49
+ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT ;
50
+
38
51
private final DestinationCache destinationCache = new DestinationCache ();
39
52
40
53
private final SessionSubscriptionRegistry subscriptionRegistry = new SessionSubscriptionRegistry ();
41
54
42
- private AntPathMatcher pathMatcher = new AntPathMatcher ();
55
+ private PathMatcher pathMatcher = new AntPathMatcher ();
56
+
43
57
44
58
45
59
/**
46
- * @param pathMatcher the pathMatcher to set
60
+ * Specify the maximum number of entries for the resolved destination cache.
61
+ * Default is 1024.
47
62
*/
48
- public void setPathMatcher (AntPathMatcher pathMatcher ) {
63
+ public void setCacheLimit (int cacheLimit ) {
64
+ this .cacheLimit = cacheLimit ;
65
+ }
66
+
67
+ /**
68
+ * Return the maximum number of entries for the resolved destination cache.
69
+ */
70
+ public int getCacheLimit () {
71
+ return this .cacheLimit ;
72
+ }
73
+
74
+ /**
75
+ * The PathMatcher to use.
76
+ */
77
+ public void setPathMatcher (PathMatcher pathMatcher ) {
49
78
this .pathMatcher = pathMatcher ;
50
79
}
51
80
52
- public AntPathMatcher getPathMatcher () {
81
+ /**
82
+ * The configured PathMatcher.
83
+ */
84
+ public PathMatcher getPathMatcher () {
53
85
return this .pathMatcher ;
54
86
}
55
87
56
88
@ Override
57
89
protected void addSubscriptionInternal (String sessionId , String subsId , String destination , Message <?> message ) {
58
- SessionSubscriptionInfo info = this .subscriptionRegistry .addSubscription (sessionId , subsId , destination );
59
- this .destinationCache .mapToDestination (destination , sessionId , subsId );
90
+ this .subscriptionRegistry .addSubscription (sessionId , subsId , destination );
91
+ this .destinationCache .updateAfterNewSubscription (destination , sessionId , subsId );
60
92
}
61
93
62
94
@ Override
@@ -65,7 +97,7 @@ protected void removeSubscriptionInternal(String sessionId, String subsId, Messa
65
97
if (info != null ) {
66
98
String destination = info .removeSubscription (subsId );
67
99
if (info .getSubscriptions (destination ) == null ) {
68
- this .destinationCache .unmapFromDestination (destination , sessionId , subsId );
100
+ this .destinationCache .updateAfterRemovedSubscription (destination , sessionId , subsId );
69
101
}
70
102
}
71
103
}
@@ -77,30 +109,28 @@ public void unregisterAllSubscriptions(String sessionId) {
77
109
if (logger .isDebugEnabled ()) {
78
110
logger .debug ("Unregistering subscriptions for sessionId=" + sessionId );
79
111
}
80
- this .destinationCache .removeSessionSubscriptions (info );
112
+ this .destinationCache .updateAfterRemovedSession (info );
81
113
}
82
114
}
83
115
84
116
@ Override
85
117
protected MultiValueMap <String , String > findSubscriptionsInternal (String destination , Message <?> message ) {
86
- MultiValueMap <String ,String > result ;
87
- if (this . destinationCache . isCachedDestination ( destination ) ) {
88
- result = this . destinationCache . getSubscriptions ( destination ) ;
118
+ MultiValueMap <String ,String > result = this . destinationCache . getSubscriptions ( destination ) ;
119
+ if (result != null ) {
120
+ return result ;
89
121
}
90
- else {
91
- result = new LinkedMultiValueMap <String , String >();
92
- for (SessionSubscriptionInfo info : this .subscriptionRegistry .getAllSubscriptions ()) {
93
- for (String destinationPattern : info .getDestinations ()) {
94
- if (this .pathMatcher .match (destinationPattern , destination )) {
95
- for (String subscriptionId : info .getSubscriptions (destinationPattern )) {
96
- result .add (info .sessionId , subscriptionId );
97
- }
122
+ result = new LinkedMultiValueMap <String , String >();
123
+ for (SessionSubscriptionInfo info : this .subscriptionRegistry .getAllSubscriptions ()) {
124
+ for (String destinationPattern : info .getDestinations ()) {
125
+ if (this .pathMatcher .match (destinationPattern , destination )) {
126
+ for (String subscriptionId : info .getSubscriptions (destinationPattern )) {
127
+ result .add (info .sessionId , subscriptionId );
98
128
}
99
129
}
100
130
}
101
- if (! result . isEmpty ()) {
102
- this . destinationCache . addSubscriptions ( destination , result );
103
- }
131
+ }
132
+ if (! result . isEmpty ()) {
133
+ this . destinationCache . addSubscriptions ( destination , result );
104
134
}
105
135
return result ;
106
136
}
@@ -112,85 +142,84 @@ public String toString() {
112
142
}
113
143
114
144
115
-
116
-
117
145
/**
118
- * Provide direct lookup of session subscriptions by destination
146
+ * A cache for destinations previously resolved via
147
+ * {@link DefaultSubscriptionRegistry#findSubscriptionsInternal(String, Message)}
119
148
*/
120
- private static class DestinationCache {
121
-
122
- private AntPathMatcher pathMatcher = new AntPathMatcher ();
149
+ private class DestinationCache {
150
+
151
+ /** Map from destination -> <sessionId, subscriptionId> */
152
+ @ SuppressWarnings ("serial" )
153
+ private final Map <String , MultiValueMap <String , String >> cache =
154
+ new LinkedHashMap <String , MultiValueMap <String , String >>(DEFAULT_CACHE_LIMIT , 0.75f , true ) {
155
+ @ Override
156
+ protected boolean removeEldestEntry (Map .Entry <String , MultiValueMap <String , String >> eldest ) {
157
+ return size () > getCacheLimit ();
158
+ }
159
+ };
123
160
124
- // destination -> ..
125
- private final Map <String , MultiValueMap <String , String >> subscriptionsByDestination =
126
- new ConcurrentHashMap <String , MultiValueMap <String , String >>();
127
161
128
- private final Object monitor = new Object ();
129
162
163
+ public MultiValueMap <String , String > getSubscriptions (String destination ) {
164
+ synchronized (this .cache ) {
165
+ return this .cache .get (destination );
166
+ }
167
+ }
130
168
131
169
public void addSubscriptions (String destination , MultiValueMap <String , String > subscriptions ) {
132
- this .subscriptionsByDestination .put (destination , subscriptions );
170
+ synchronized (this .cache ) {
171
+ this .cache .put (destination , subscriptions );
172
+ }
133
173
}
134
174
135
- public void mapToDestination (String destination , String sessionId , String subsId ) {
136
- synchronized (this .monitor ) {
137
- for (String cachedDestination : this .subscriptionsByDestination .keySet ()) {
138
- if (this .pathMatcher .match (destination , cachedDestination )) {
139
- MultiValueMap <String , String > registrations = this .subscriptionsByDestination .get (cachedDestination );
140
- if (registrations == null ) {
141
- registrations = new LinkedMultiValueMap <String , String >();
142
- }
143
- registrations .add (sessionId , subsId );
175
+ public void updateAfterNewSubscription (String destination , String sessionId , String subsId ) {
176
+ synchronized (this .cache ) {
177
+ for (String cachedDestination : this .cache .keySet ()) {
178
+ if (getPathMatcher ().match (destination , cachedDestination )) {
179
+ MultiValueMap <String , String > subscriptions = this .cache .get (cachedDestination );
180
+ subscriptions .add (sessionId , subsId );
144
181
}
145
182
}
146
183
}
147
184
}
148
185
149
- public void unmapFromDestination (String destination , String sessionId , String subsId ) {
150
- synchronized (this .monitor ) {
151
- for (String cachedDestination : this .subscriptionsByDestination .keySet ()) {
152
- if (this . pathMatcher .match (destination , cachedDestination )) {
153
- MultiValueMap <String , String > registrations = this .subscriptionsByDestination .get (cachedDestination );
154
- List <String > subscriptions = registrations .get (sessionId );
155
- while ( subscriptions .remove (subsId ) );
156
- if (subscriptions .isEmpty ()) {
157
- registrations .remove (sessionId );
186
+ public void updateAfterRemovedSubscription (String destination , String sessionId , String subsId ) {
187
+ synchronized (this .cache ) {
188
+ for (String cachedDestination : this .cache .keySet ()) {
189
+ if (getPathMatcher () .match (destination , cachedDestination )) {
190
+ MultiValueMap <String , String > subscriptions = this .cache .get (cachedDestination );
191
+ List <String > subsIds = subscriptions .get (sessionId );
192
+ subsIds .remove (subsId );
193
+ if (subsIds .isEmpty ()) {
194
+ subscriptions .remove (sessionId );
158
195
}
159
- if (registrations .isEmpty ()) {
160
- this .subscriptionsByDestination .remove (cachedDestination );
196
+ if (subscriptions .isEmpty ()) {
197
+ this .cache .remove (cachedDestination );
161
198
}
162
199
}
163
200
}
164
201
}
165
202
}
166
203
167
- public void removeSessionSubscriptions (SessionSubscriptionInfo info ) {
168
- synchronized (this .monitor ) {
204
+ public void updateAfterRemovedSession (SessionSubscriptionInfo info ) {
205
+ synchronized (this .cache ) {
169
206
for (String destination : info .getDestinations ()) {
170
- for (String cachedDestination : this .subscriptionsByDestination .keySet ()) {
171
- if (this . pathMatcher .match (destination , cachedDestination )) {
172
- MultiValueMap <String , String > map = this .subscriptionsByDestination .get (cachedDestination );
207
+ for (String cachedDestination : this .cache .keySet ()) {
208
+ if (getPathMatcher () .match (destination , cachedDestination )) {
209
+ MultiValueMap <String , String > map = this .cache .get (cachedDestination );
173
210
map .remove (info .getSessionId ());
174
211
if (map .isEmpty ()) {
175
- this .subscriptionsByDestination .remove (cachedDestination );
212
+ this .cache .remove (cachedDestination );
176
213
}
177
214
}
178
215
}
179
216
}
180
217
}
181
218
}
182
219
183
- public MultiValueMap <String , String > getSubscriptions (String destination ) {
184
- return this .subscriptionsByDestination .get (destination );
185
- }
186
-
187
- public boolean isCachedDestination (String destination ) {
188
- return subscriptionsByDestination .containsKey (destination );
189
- }
190
-
191
220
@ Override
192
221
public String toString () {
193
- return "[subscriptionsByDestination =" + this .subscriptionsByDestination + "]" ;
222
+ return "[cache =" + this .cache + "]" ;
194
223
}
195
224
}
196
225
@@ -199,6 +228,7 @@ public String toString() {
199
228
*/
200
229
private static class SessionSubscriptionRegistry {
201
230
231
+ // sessionId -> SessionSubscriptionInfo
202
232
private final ConcurrentMap <String , SessionSubscriptionInfo > sessions =
203
233
new ConcurrentHashMap <String , SessionSubscriptionInfo >();
204
234
@@ -241,6 +271,7 @@ private static class SessionSubscriptionInfo {
241
271
242
272
private final String sessionId ;
243
273
274
+ // destination -> subscriptionIds
244
275
private final Map <String , Set <String >> subscriptions = new ConcurrentHashMap <String , Set <String >>(4 );
245
276
246
277
private final Object monitor = new Object ();
0 commit comments