From 03e3ef53abf0a5eb34fae8fd5fc4b0c38876eb10 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 29 Jan 2016 16:10:02 -0500 Subject: [PATCH] @SendToUser supported on the class level Issue: SPR-12047 --- .../messaging/simp/annotation/SendToUser.java | 7 +- .../SendToMethodReturnValueHandler.java | 17 +++- .../SendToMethodReturnValueHandlerTests.java | 83 +++++++++++++++++-- src/asciidoc/web-websocket.adoc | 3 +- src/asciidoc/whats-new.adoc | 2 +- 5 files changed, 101 insertions(+), 11 deletions(-) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java index 06f2133e32a..255e1238eae 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SendToUser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -30,6 +30,9 @@ import org.springframework.core.annotation.AliasFor; * destination(s) prepended with "/user/{username}" where the user name * is extracted from the headers of the input message being handled. * + *

The annotation may also be placed at class-level in which case all methods + * in the class where the annotation applies will inherit it. + * @author Rossen Stoyanchev * @author Sam Brannen * @since 4.0 @@ -37,7 +40,7 @@ import org.springframework.core.annotation.AliasFor; * @see org.springframework.messaging.simp.user.UserDestinationMessageHandler * @see org.springframework.messaging.simp.SimpMessageHeaderAccessor#getUser() */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SendToUser { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java index 227844d9dd1..1fe4a35533b 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandler.java @@ -134,7 +134,8 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH public boolean supportsReturnType(MethodParameter returnType) { if (returnType.getMethodAnnotation(SendTo.class) != null || AnnotationUtils.getAnnotation(returnType.getDeclaringClass(), SendTo.class) != null || - returnType.getMethodAnnotation(SendToUser.class) != null) { + returnType.getMethodAnnotation(SendToUser.class) != null || + AnnotationUtils.getAnnotation(returnType.getDeclaringClass(), SendToUser.class) != null) { return true; } return (!this.annotationRequired); @@ -149,7 +150,7 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH MessageHeaders headers = message.getHeaders(); String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); PlaceholderResolver varResolver = initVarResolver(headers); - SendToUser sendToUser = returnType.getMethodAnnotation(SendToUser.class); + SendToUser sendToUser = getSendToUser(returnType); if (sendToUser != null) { boolean broadcast = sendToUser.broadcast(); @@ -184,6 +185,18 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH } } + private SendToUser getSendToUser(MethodParameter returnType) { + SendToUser annot = returnType.getMethodAnnotation(SendToUser.class); + if (annot != null && !ObjectUtils.isEmpty((annot.value()))) { + return annot; + } + SendToUser typeAnnot = AnnotationUtils.getAnnotation(returnType.getDeclaringClass(), SendToUser.class); + if (typeAnnot != null && !ObjectUtils.isEmpty((typeAnnot.value()))) { + return typeAnnot; + } + return (annot != null ? annot : typeAnnot); + } + private SendTo getSendTo(MethodParameter returnType) { SendTo sendTo = returnType.getMethodAnnotation(SendTo.class); if (sendTo != null && !ObjectUtils.isEmpty((sendTo.value()))) { diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java index 965f70baeae..d14587bdb03 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SendToMethodReturnValueHandlerTests.java @@ -91,6 +91,9 @@ public class SendToMethodReturnValueHandlerTests { private MethodParameter defaultNoAnnotation; private MethodParameter defaultEmptyAnnotation; private MethodParameter defaultOverrideAnnotation; + private MethodParameter userDefaultNoAnnotation; + private MethodParameter userDefaultEmptyAnnotation; + private MethodParameter userDefaultOverrideAnnotation; @Before @@ -133,14 +136,23 @@ public class SendToMethodReturnValueHandlerTests { method = this.getClass().getDeclaredMethod("handleAndSendToJsonView"); this.jsonViewReturnType = new SynthesizingMethodParameter(method, -1); - method = TestBean.class.getDeclaredMethod("handleNoAnnotation"); + method = SendToTestBean.class.getDeclaredMethod("handleNoAnnotation"); this.defaultNoAnnotation = new SynthesizingMethodParameter(method, -1); - method = TestBean.class.getDeclaredMethod("handleAndSendToDefaultDestination"); + method = SendToTestBean.class.getDeclaredMethod("handleAndSendToDefaultDestination"); this.defaultEmptyAnnotation = new SynthesizingMethodParameter(method, -1); - method = TestBean.class.getDeclaredMethod("handleAndSendToOverride"); + method = SendToTestBean.class.getDeclaredMethod("handleAndSendToOverride"); this.defaultOverrideAnnotation = new SynthesizingMethodParameter(method, -1); + + method = SendToUserTestBean.class.getDeclaredMethod("handleNoAnnotation"); + this.userDefaultNoAnnotation = new SynthesizingMethodParameter(method, -1); + + method = SendToUserTestBean.class.getDeclaredMethod("handleAndSendToDefaultDestination"); + this.userDefaultEmptyAnnotation = new SynthesizingMethodParameter(method, -1); + + method = SendToUserTestBean.class.getDeclaredMethod("handleAndSendToOverride"); + this.userDefaultOverrideAnnotation = new SynthesizingMethodParameter(method, -1); } @@ -154,6 +166,10 @@ public class SendToMethodReturnValueHandlerTests { assertTrue(this.handler.supportsReturnType(this.defaultNoAnnotation)); assertTrue(this.handler.supportsReturnType(this.defaultEmptyAnnotation)); assertTrue(this.handler.supportsReturnType(this.defaultOverrideAnnotation)); + + assertTrue(this.handler.supportsReturnType(this.userDefaultNoAnnotation)); + assertTrue(this.handler.supportsReturnType(this.userDefaultEmptyAnnotation)); + assertTrue(this.handler.supportsReturnType(this.userDefaultOverrideAnnotation)); } @Test @@ -230,6 +246,44 @@ public class SendToMethodReturnValueHandlerTests { assertResponse(this.defaultOverrideAnnotation, sessionId, 1, "/dest4"); } + @Test + public void sendToUserClassDefaultNoAnnotation() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", null, null, null); + this.handler.handleReturnValue(PAYLOAD, this.userDefaultNoAnnotation, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + assertResponse(this.userDefaultNoAnnotation, sessionId, 0, "/user/sess1/dest-default"); + } + + @Test + public void sendToUserClassDefaultEmptyAnnotation() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", null, null, null); + this.handler.handleReturnValue(PAYLOAD, this.userDefaultEmptyAnnotation, inputMessage); + + verify(this.messageChannel, times(1)).send(this.messageCaptor.capture()); + assertResponse(this.userDefaultEmptyAnnotation, sessionId, 0, "/user/sess1/dest-default"); + } + + @Test + public void sendToUserClassDefaultOverride() throws Exception { + given(this.messageChannel.send(any(Message.class))).willReturn(true); + + String sessionId = "sess1"; + Message inputMessage = createInputMessage(sessionId, "sub1", null, null, null); + this.handler.handleReturnValue(PAYLOAD, this.userDefaultOverrideAnnotation, inputMessage); + + verify(this.messageChannel, times(2)).send(this.messageCaptor.capture()); + assertResponse(this.userDefaultOverrideAnnotation, sessionId, 0, "/user/sess1/dest3"); + assertResponse(this.userDefaultOverrideAnnotation, sessionId, 1, "/user/sess1/dest4"); + } + + private void assertResponse(MethodParameter methodParameter, String sessionId, int index, String destination) { SimpMessageHeaderAccessor accessor = getCapturedAccessor(index); @@ -537,8 +591,8 @@ public class SendToMethodReturnValueHandlerTests { return payload; } - @SendTo("/dest-default") - private static class TestBean { + @SendTo("/dest-default") @SuppressWarnings("unused") + private static class SendToTestBean { public String handleNoAnnotation() { return PAYLOAD; @@ -556,6 +610,25 @@ public class SendToMethodReturnValueHandlerTests { } + @SendToUser("/dest-default") @SuppressWarnings("unused") + private static class SendToUserTestBean { + + public String handleNoAnnotation() { + return PAYLOAD; + } + + @SendToUser + public String handleAndSendToDefaultDestination() { + return PAYLOAD; + } + + @SendToUser({"/dest3", "/dest4"}) + public String handleAndSendToOverride() { + return PAYLOAD; + } + + } + private interface MyJacksonView1 {} private interface MyJacksonView2 {} diff --git a/src/asciidoc/web-websocket.adoc b/src/asciidoc/web-websocket.adoc index dfeb28df690..3d2583a0a23 100644 --- a/src/asciidoc/web-websocket.adoc +++ b/src/asciidoc/web-websocket.adoc @@ -1700,7 +1700,8 @@ than their name and the generic destination. This is also supported through an annotation as well as a messaging template. For example, a message-handling method can send messages to the user associated with -the message being handled through the `@SendToUser` annotation: +the message being handled through the `@SendToUser` annotation (also supported on +the class-level to share a common destination): [source,java,indent=0] [subs="verbatim,quotes"] diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc index 069b7efc581..c1971e7fd73 100644 --- a/src/asciidoc/whats-new.adoc +++ b/src/asciidoc/whats-new.adoc @@ -671,7 +671,7 @@ Spring 4.3 also improves the caching abstraction as follows: === WebSocket Messaging Improvements -* `@SendTo` can now be specified at class-level to share a common destination. +* `@SendTo` and `@SendToUser` can now be specified at class-level to share a common destination. === Testing Improvements