Skip to content

Commit 4b4efa9

Browse files
committed
Placeholder support for STOMP @MessageMapping methods
Issue: SPR-13271
1 parent a65e036 commit 4b4efa9

File tree

2 files changed

+68
-13
lines changed

2 files changed

+68
-13
lines changed

spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
2929
import org.springframework.context.ConfigurableApplicationContext;
30+
import org.springframework.context.EmbeddedValueResolverAware;
3031
import org.springframework.context.SmartLifecycle;
3132
import org.springframework.core.annotation.AnnotationUtils;
3233
import org.springframework.core.convert.ConversionService;
@@ -69,6 +70,7 @@
6970
import org.springframework.util.ClassUtils;
7071
import org.springframework.util.CollectionUtils;
7172
import org.springframework.util.PathMatcher;
73+
import org.springframework.util.StringValueResolver;
7274
import org.springframework.validation.Validator;
7375

7476
/**
@@ -82,7 +84,7 @@
8284
* @since 4.0
8385
*/
8486
public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHandler<SimpMessageMappingInfo>
85-
implements SmartLifecycle {
87+
implements EmbeddedValueResolverAware, SmartLifecycle {
8688

8789
private static final boolean completableFuturePresent = ClassUtils.isPresent("java.util.concurrent.CompletableFuture",
8890
SimpAnnotationMethodMessageHandler.class.getClassLoader());
@@ -104,6 +106,8 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
104106

105107
private Validator validator;
106108

109+
private StringValueResolver valueResolver;
110+
107111
private MessageHeaderInitializer headerInitializer;
108112

109113
private final Object lifecycleMonitor = new Object();
@@ -234,6 +238,11 @@ public void setValidator(Validator validator) {
234238
this.validator = validator;
235239
}
236240

241+
@Override
242+
public void setEmbeddedValueResolver(StringValueResolver resolver) {
243+
this.valueResolver = resolver;
244+
}
245+
237246
/**
238247
* Configure a {@link MessageHeaderInitializer} to pass on to
239248
* {@link org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler}s
@@ -376,13 +385,30 @@ protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> han
376385
}
377386

378387
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation) {
388+
String[] destinations = resolveEmbeddedValuesInDestinations(annotation.value());
379389
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
380-
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
390+
new DestinationPatternsMessageCondition(destinations, this.pathMatcher));
381391
}
382392

383393
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) {
394+
String[] destinations = resolveEmbeddedValuesInDestinations(annotation.value());
384395
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
385-
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
396+
new DestinationPatternsMessageCondition(destinations, this.pathMatcher));
397+
}
398+
399+
/**
400+
* Resolve placeholder values in the given array of destinations.
401+
* @return a new array with updated destinations
402+
*/
403+
protected String[] resolveEmbeddedValuesInDestinations(String[] destinations) {
404+
if (this.valueResolver == null) {
405+
return destinations;
406+
}
407+
String[] result = new String[destinations.length];
408+
for (int i = 0; i < destinations.length; i++) {
409+
result[i] = this.valueResolver.resolveStringValue(destinations[i]);
410+
}
411+
return result;
386412
}
387413

388414
@Override

spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@
2828
import org.junit.Before;
2929
import org.junit.Test;
3030
import org.mockito.ArgumentCaptor;
31-
import static org.mockito.BDDMockito.given;
3231
import org.mockito.Captor;
33-
import static org.mockito.Matchers.any;
34-
import static org.mockito.Matchers.anyObject;
3532
import org.mockito.Mock;
33+
import org.mockito.MockitoAnnotations;
3634

3735
import org.springframework.context.support.StaticApplicationContext;
3836
import org.springframework.messaging.Message;
@@ -62,10 +60,16 @@
6260
import org.springframework.validation.Validator;
6361
import org.springframework.validation.annotation.Validated;
6462

65-
import static org.hamcrest.Matchers.*;
66-
import static org.junit.Assert.*;
63+
import static org.hamcrest.Matchers.is;
64+
import static org.junit.Assert.assertEquals;
65+
import static org.junit.Assert.assertNotNull;
66+
import static org.junit.Assert.assertNull;
67+
import static org.junit.Assert.assertThat;
68+
import static org.junit.Assert.assertTrue;
69+
import static org.mockito.BDDMockito.given;
70+
import static org.mockito.Matchers.any;
71+
import static org.mockito.Matchers.anyObject;
6772
import static org.mockito.Mockito.verify;
68-
import org.mockito.MockitoAnnotations;
6973

7074
/**
7175
* Test fixture for
@@ -107,8 +111,7 @@ public void setup() {
107111
this.messageHandler.setValidator(new StringTestValidator(TEST_INVALID_VALUE));
108112
this.messageHandler.afterPropertiesSet();
109113

110-
testController = new TestController();
111-
this.messageHandler.registerHandler(this.testController);
114+
this.testController = new TestController();
112115
}
113116

114117

@@ -117,6 +120,7 @@ public void setup() {
117120
public void headerArgumentResolution() {
118121
Map<String, Object> headers = Collections.singletonMap("foo", "bar");
119122
Message<?> message = createMessage("/pre/headers", headers);
123+
this.messageHandler.registerHandler(this.testController);
120124
this.messageHandler.handleMessage(message);
121125

122126
assertEquals("headers", this.testController.method);
@@ -128,6 +132,7 @@ public void headerArgumentResolution() {
128132
public void optionalHeaderArgumentResolutionWhenPresent() {
129133
Map<String, Object> headers = Collections.singletonMap("foo", "bar");
130134
Message<?> message = createMessage("/pre/optionalHeaders", headers);
135+
this.messageHandler.registerHandler(this.testController);
131136
this.messageHandler.handleMessage(message);
132137

133138
assertEquals("optionalHeaders", this.testController.method);
@@ -138,6 +143,7 @@ public void optionalHeaderArgumentResolutionWhenPresent() {
138143
@Test
139144
public void optionalHeaderArgumentResolutionWhenNotPresent() {
140145
Message<?> message = createMessage("/pre/optionalHeaders");
146+
this.messageHandler.registerHandler(this.testController);
141147
this.messageHandler.handleMessage(message);
142148

143149
assertEquals("optionalHeaders", this.testController.method);
@@ -148,6 +154,7 @@ public void optionalHeaderArgumentResolutionWhenNotPresent() {
148154
@Test
149155
public void messageMappingDestinationVariableResolution() {
150156
Message<?> message = createMessage("/pre/message/bar/value");
157+
this.messageHandler.registerHandler(this.testController);
151158
this.messageHandler.handleMessage(message);
152159

153160
assertEquals("messageMappingDestinationVariable", this.testController.method);
@@ -158,6 +165,7 @@ public void messageMappingDestinationVariableResolution() {
158165
@Test
159166
public void subscribeEventDestinationVariableResolution() {
160167
Message<?> message = createMessage("/pre/sub/bar/value");
168+
this.messageHandler.registerHandler(this.testController);
161169
this.messageHandler.handleMessage(message);
162170

163171
assertEquals("subscribeEventDestinationVariable", this.testController.method);
@@ -168,6 +176,7 @@ public void subscribeEventDestinationVariableResolution() {
168176
@Test
169177
public void simpleBinding() {
170178
Message<?> message = createMessage("/pre/binding/id/12");
179+
this.messageHandler.registerHandler(this.testController);
171180
this.messageHandler.handleMessage(message);
172181

173182
assertEquals("simpleBinding", this.testController.method);
@@ -178,13 +187,16 @@ public void simpleBinding() {
178187
@Test
179188
public void validationError() {
180189
Message<?> message = createMessage("/pre/validation/payload");
190+
this.messageHandler.registerHandler(this.testController);
181191
this.messageHandler.handleMessage(message);
192+
182193
assertEquals("handleValidationException", this.testController.method);
183194
}
184195

185196
@Test
186197
public void exceptionWithHandlerMethodArg() {
187198
Message<?> message = createMessage("/pre/illegalState");
199+
this.messageHandler.registerHandler(this.testController);
188200
this.messageHandler.handleMessage(message);
189201

190202
assertEquals("handleExceptionWithHandlerMethodArg", this.testController.method);
@@ -203,6 +215,7 @@ public void simpScope() {
203215
headers.setSessionAttributes(sessionAttributes);
204216
headers.setDestination("/pre/scope");
205217
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
218+
this.messageHandler.registerHandler(this.testController);
206219
this.messageHandler.handleMessage(message);
207220

208221
assertEquals("scope", this.testController.method);
@@ -217,6 +230,7 @@ public void dotPathSeparator() {
217230
this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/"));
218231

219232
Message<?> message = createMessage("/app1/pre.foo");
233+
this.messageHandler.registerHandler(this.testController);
220234
this.messageHandler.handleMessage(message);
221235

222236
assertEquals("handleFoo", controller.method);
@@ -234,7 +248,6 @@ public void listenableFutureSuccess() {
234248
given(this.channel.send(any(Message.class))).willReturn(true);
235249
given(this.converter.toMessage(anyObject(), any(MessageHeaders.class))).willReturn(emptyMessage);
236250

237-
238251
ListenableFutureController controller = new ListenableFutureController();
239252
this.messageHandler.registerHandler(controller);
240253
this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/"));
@@ -304,6 +317,17 @@ public void completableFutureFailure() {
304317
assertTrue(controller.exceptionCaught);
305318
}
306319

320+
@Test
321+
public void placeholder() throws Exception {
322+
Message<?> message = createMessage("/pre/myValue");
323+
this.messageHandler.setEmbeddedValueResolver(value -> ("/${myProperty}".equals(value) ? "/myValue" : value));
324+
this.messageHandler.registerHandler(this.testController);
325+
this.messageHandler.handleMessage(message);
326+
327+
assertEquals("placeholder", this.testController.method);
328+
}
329+
330+
307331
private Message<?> createMessage(String destination) {
308332
return createMessage(destination, null);
309333
}
@@ -409,6 +433,11 @@ public void scope() {
409433
assertThat(simpAttributes.getAttribute("name"), is("value"));
410434
this.method = "scope";
411435
}
436+
437+
@MessageMapping("/${myProperty}")
438+
public void placeholder() {
439+
this.method = "placeholder";
440+
}
412441
}
413442

414443
@Controller

0 commit comments

Comments
 (0)