4

I am working with Spring-websocket and I have the following problem:

I am trying to put a placeholder inside a @MessageMapping annotation in order to get the url from properties. It works with @RequestMapping but not with @MessageMapping.

If I use this placeholder, the URL is null. Any idea or suggestion?

Example:

@RequestMapping(value= "${myProperty}")

@MessageMapping("${myProperty}")
crm86
  • 1,394
  • 1
  • 21
  • 44

4 Answers4

2

Rossen Stoyanchev added placeholder support for @MessageMapping and @SubscribeMapping methods.

See Jira issue: https://jira.spring.io/browse/SPR-13271

crm86
  • 1,394
  • 1
  • 21
  • 44
1

Spring allows you to use property placeholders in @RequestMapping, but not in @MessageMapping. This is 'cause the MessageHandler. So, we need to override the default MessageHandler to do this.

WebSocketAnnotationMethodMessageHandler does not support placeholders and you need add this support yourself.

For simplicity I just created another WebSocketAnnotationMethodMessageHandler class in my project at the same package of the original, org.springframework.web.socket.messaging, and override getMappingForMethod method from SimpAnnotationMethodMessageHandler with same content, changing only how SimpMessageMappingInfo is contructed using this with this methods (private in WebSocketAnnotationMethodMessageHandler):

private SimpMessageMappingInfo createMessageMappingCondition(final MessageMapping annotation) {
    return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, new DestinationPatternsMessageCondition(
            this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}

private SimpMessageMappingInfo createSubscribeCondition(final SubscribeMapping annotation) {
    final SimpMessageTypeMessageCondition messageTypeMessageCondition = SimpMessageTypeMessageCondition.SUBSCRIBE;
    return new SimpMessageMappingInfo(messageTypeMessageCondition, new DestinationPatternsMessageCondition(
            this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}

These methods now will resolve value considering properties (calling resolveAnnotationValues method), so we need use something like this:

private String[] resolveAnnotationValues(final String[] destinationNames) {
    final int length = destinationNames.length;
    final String[] result = new String[length];

    for (int i = 0; i < length; i++) {
        result[i] = this.resolveAnnotationValue(destinationNames[i]);
    }

    return result;
}

private String resolveAnnotationValue(final String name) {
    if (!(this.getApplicationContext() instanceof ConfigurableApplicationContext)) {
        return name;
    }

    final ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) this.getApplicationContext();
    final ConfigurableBeanFactory configurableBeanFactory = applicationContext.getBeanFactory();

    final String placeholdersResolved = configurableBeanFactory.resolveEmbeddedValue(name);
    final BeanExpressionResolver exprResolver = configurableBeanFactory.getBeanExpressionResolver();
    if (exprResolver == null) {
        return name;
    }
    final Object result = exprResolver.evaluate(placeholdersResolved, new BeanExpressionContext(configurableBeanFactory, null));
    return result != null ? result.toString() : name;
}

You still need to define a PropertySourcesPlaceholderConfigurer bean in your configuration.

If you are using XML based configuration, include something like this:

<context:property-placeholder location="classpath:/META-INF/spring/url-mapping-config.properties" />

If you are using Java based configuration, you can try in this way:

@Configuration
@PropertySources(value = @PropertySource("classpath:/META-INF/spring/url-mapping-config.properties"))
public class URLMappingConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}

Obs.: in this case, url-mapping-config.properties file are in a gradle/maven project in src\main\resources\META-INF\spring folder and content look like this:

myPropertyWS=urlvaluews

This is my sample controller:

@Controller
public class WebSocketController {

    @SendTo("/topic/test")
    @MessageMapping("${myPropertyWS}")
    public String test() throws Exception {
        Thread.sleep(4000); // simulated delay
        return "OK";
    }

}

With default MessageHandler startup log will print something like this:

INFO: Mapped "{[/${myPropertyWS}],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception

And with our MessageHandler now print this:

INFO: Mapped "{[/urlvaluews],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception

See in this gist the full WebSocketAnnotationMethodMessageHandler implementation.

EDIT: this solution resolves the problem for versions before 4.2 GA. For more information, see this jira.

Bruno Ribeiro
  • 5,956
  • 5
  • 39
  • 48
0

Update :

Now I understood what you mean, but I think that is not possible(yet).

Documentation does not mention anything related to Path mapping URIs.

enter image description here

Old answer

Use

   @MessageMapping("/handler/{myProperty}")

instead of

   @MessageMapping("/handler/${myProperty}")

And use it like this:

   @MessageMapping("/myHandler/{username}")
   public void handleTextMessage(@DestinationVariable String username,Message message) {
        //do something
   }
Karthik
  • 4,950
  • 6
  • 35
  • 65
  • This not sets the URL, this is just for pass a parameter. I would like to indicate the URL by properties, I mean ${myProperty} indicates the complete URL – crm86 Jul 04 '15 at 15:42
-1
@MessageMapping("/chat/{roomId}")
public Message handleMessages(@DestinationVariable("roomId") String roomId, @Payload Message message, Traveler traveler) throws Exception {
    System.out.println("Message received for room: " + roomId);
    System.out.println("User: " + traveler.toString());

    // store message in database
    message.setAuthor(traveler);
    message.setChatRoomId(Integer.parseInt(roomId));
    int id = MessageRepository.getInstance().save(message);
    message.setId(id);

    return message;
}
Zhong Ri
  • 2,556
  • 1
  • 19
  • 23