001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.messaging.simp.annotation.support; 018 019import java.lang.annotation.Annotation; 020import java.security.Principal; 021import java.util.Map; 022 023import org.springframework.core.MethodParameter; 024import org.springframework.core.annotation.AnnotatedElementUtils; 025import org.springframework.core.annotation.AnnotationUtils; 026import org.springframework.messaging.Message; 027import org.springframework.messaging.MessageChannel; 028import org.springframework.messaging.MessageHeaders; 029import org.springframework.messaging.handler.DestinationPatternsMessageCondition; 030import org.springframework.messaging.handler.annotation.SendTo; 031import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver; 032import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; 033import org.springframework.messaging.simp.SimpMessageHeaderAccessor; 034import org.springframework.messaging.simp.SimpMessageSendingOperations; 035import org.springframework.messaging.simp.SimpMessageType; 036import org.springframework.messaging.simp.SimpMessagingTemplate; 037import org.springframework.messaging.simp.annotation.SendToUser; 038import org.springframework.messaging.simp.user.DestinationUserNameProvider; 039import org.springframework.messaging.support.MessageHeaderInitializer; 040import org.springframework.util.Assert; 041import org.springframework.util.ObjectUtils; 042import org.springframework.util.PropertyPlaceholderHelper; 043import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; 044import org.springframework.util.StringUtils; 045 046/** 047 * A {@link HandlerMethodReturnValueHandler} for sending to destinations specified in a 048 * {@link SendTo} or {@link SendToUser} method-level annotations. 049 * 050 * <p>The value returned from the method is converted, and turned to a {@link Message} and 051 * sent through the provided {@link MessageChannel}. The message is then enriched with the 052 * session id of the input message as well as the destination from the annotation(s). 053 * If multiple destinations are specified, a copy of the message is sent to each destination. 054 * 055 * @author Rossen Stoyanchev 056 * @author Sebastien Deleuze 057 * @since 4.0 058 */ 059public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueHandler { 060 061 private final SimpMessageSendingOperations messagingTemplate; 062 063 private final boolean annotationRequired; 064 065 private String defaultDestinationPrefix = "/topic"; 066 067 private String defaultUserDestinationPrefix = "/queue"; 068 069 private PropertyPlaceholderHelper placeholderHelper = new PropertyPlaceholderHelper("{", "}", null, false); 070 071 private MessageHeaderInitializer headerInitializer; 072 073 074 public SendToMethodReturnValueHandler(SimpMessageSendingOperations messagingTemplate, boolean annotationRequired) { 075 Assert.notNull(messagingTemplate, "'messagingTemplate' must not be null"); 076 this.messagingTemplate = messagingTemplate; 077 this.annotationRequired = annotationRequired; 078 } 079 080 081 /** 082 * Configure a default prefix to add to message destinations in cases where a method 083 * is not annotated with {@link SendTo @SendTo} or does not specify any destinations 084 * through the annotation's value attribute. 085 * <p>By default, the prefix is set to "/topic". 086 */ 087 public void setDefaultDestinationPrefix(String defaultDestinationPrefix) { 088 this.defaultDestinationPrefix = defaultDestinationPrefix; 089 } 090 091 /** 092 * Return the configured default destination prefix. 093 * @see #setDefaultDestinationPrefix(String) 094 */ 095 public String getDefaultDestinationPrefix() { 096 return this.defaultDestinationPrefix; 097 } 098 099 /** 100 * Configure a default prefix to add to message destinations in cases where a 101 * method is annotated with {@link SendToUser @SendToUser} but does not specify 102 * any destinations through the annotation's value attribute. 103 * <p>By default, the prefix is set to "/queue". 104 */ 105 public void setDefaultUserDestinationPrefix(String prefix) { 106 this.defaultUserDestinationPrefix = prefix; 107 } 108 109 /** 110 * Return the configured default user destination prefix. 111 * @see #setDefaultUserDestinationPrefix(String) 112 */ 113 public String getDefaultUserDestinationPrefix() { 114 return this.defaultUserDestinationPrefix; 115 } 116 117 /** 118 * Configure a {@link MessageHeaderInitializer} to apply to the headers of all 119 * messages sent to the client outbound channel. 120 * <p>By default this property is not set. 121 */ 122 public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) { 123 this.headerInitializer = headerInitializer; 124 } 125 126 /** 127 * Return the configured header initializer. 128 */ 129 public MessageHeaderInitializer getHeaderInitializer() { 130 return this.headerInitializer; 131 } 132 133 134 @Override 135 public boolean supportsReturnType(MethodParameter returnType) { 136 return (returnType.hasMethodAnnotation(SendTo.class) || 137 AnnotatedElementUtils.hasAnnotation(returnType.getDeclaringClass(), SendTo.class) || 138 returnType.hasMethodAnnotation(SendToUser.class) || 139 AnnotatedElementUtils.hasAnnotation(returnType.getDeclaringClass(), SendToUser.class) || 140 !this.annotationRequired); 141 } 142 143 @Override 144 public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message) 145 throws Exception { 146 147 if (returnValue == null) { 148 return; 149 } 150 151 MessageHeaders headers = message.getHeaders(); 152 String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); 153 PlaceholderResolver varResolver = initVarResolver(headers); 154 Object annotation = findAnnotation(returnType); 155 156 if (annotation instanceof SendToUser) { 157 SendToUser sendToUser = (SendToUser) annotation; 158 boolean broadcast = sendToUser.broadcast(); 159 String user = getUserName(message, headers); 160 if (user == null) { 161 if (sessionId == null) { 162 throw new MissingSessionUserException(message); 163 } 164 user = sessionId; 165 broadcast = false; 166 } 167 String[] destinations = getTargetDestinations(sendToUser, message, this.defaultUserDestinationPrefix); 168 for (String destination : destinations) { 169 destination = this.placeholderHelper.replacePlaceholders(destination, varResolver); 170 if (broadcast) { 171 this.messagingTemplate.convertAndSendToUser( 172 user, destination, returnValue, createHeaders(null, returnType)); 173 } 174 else { 175 this.messagingTemplate.convertAndSendToUser( 176 user, destination, returnValue, createHeaders(sessionId, returnType)); 177 } 178 } 179 } 180 else { 181 SendTo sendTo = (SendTo) annotation; // possibly null 182 String[] destinations = getTargetDestinations(sendTo, message, this.defaultDestinationPrefix); 183 for (String destination : destinations) { 184 destination = this.placeholderHelper.replacePlaceholders(destination, varResolver); 185 this.messagingTemplate.convertAndSend(destination, returnValue, createHeaders(sessionId, returnType)); 186 } 187 } 188 } 189 190 private Object findAnnotation(MethodParameter returnType) { 191 Annotation[] anns = new Annotation[4]; 192 anns[0] = AnnotatedElementUtils.findMergedAnnotation(returnType.getMethod(), SendToUser.class); 193 anns[1] = AnnotatedElementUtils.findMergedAnnotation(returnType.getMethod(), SendTo.class); 194 anns[2] = AnnotatedElementUtils.findMergedAnnotation(returnType.getDeclaringClass(), SendToUser.class); 195 anns[3] = AnnotatedElementUtils.findMergedAnnotation(returnType.getDeclaringClass(), SendTo.class); 196 197 if (anns[0] != null && !ObjectUtils.isEmpty(((SendToUser) anns[0]).value())) { 198 return anns[0]; 199 } 200 if (anns[1] != null && !ObjectUtils.isEmpty(((SendTo) anns[1]).value())) { 201 return anns[1]; 202 } 203 if (anns[2] != null && !ObjectUtils.isEmpty(((SendToUser) anns[2]).value())) { 204 return anns[2]; 205 } 206 if (anns[3] != null && !ObjectUtils.isEmpty(((SendTo) anns[3]).value())) { 207 return anns[3]; 208 } 209 210 for (int i=0; i < 4; i++) { 211 if (anns[i] != null) { 212 return anns[i]; 213 } 214 } 215 216 return null; 217 } 218 219 @SuppressWarnings("unchecked") 220 private PlaceholderResolver initVarResolver(MessageHeaders headers) { 221 String name = DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER; 222 Map<String, String> vars = (Map<String, String>) headers.get(name); 223 return new DestinationVariablePlaceholderResolver(vars); 224 } 225 226 protected String getUserName(Message<?> message, MessageHeaders headers) { 227 Principal principal = SimpMessageHeaderAccessor.getUser(headers); 228 if (principal != null) { 229 return (principal instanceof DestinationUserNameProvider ? 230 ((DestinationUserNameProvider) principal).getDestinationUserName() : principal.getName()); 231 } 232 return null; 233 } 234 235 protected String[] getTargetDestinations(Annotation annotation, Message<?> message, String defaultPrefix) { 236 if (annotation != null) { 237 String[] value = (String[]) AnnotationUtils.getValue(annotation); 238 if (!ObjectUtils.isEmpty(value)) { 239 return value; 240 } 241 } 242 String name = DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER; 243 String destination = (String) message.getHeaders().get(name); 244 if (!StringUtils.hasText(destination)) { 245 throw new IllegalStateException("No lookup destination header in " + message); 246 } 247 248 return (destination.startsWith("/") ? 249 new String[] {defaultPrefix + destination} : new String[] {defaultPrefix + '/' + destination}); 250 } 251 252 private MessageHeaders createHeaders(String sessionId, MethodParameter returnType) { 253 SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); 254 if (getHeaderInitializer() != null) { 255 getHeaderInitializer().initHeaders(headerAccessor); 256 } 257 if (sessionId != null) { 258 headerAccessor.setSessionId(sessionId); 259 } 260 headerAccessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType); 261 headerAccessor.setLeaveMutable(true); 262 return headerAccessor.getMessageHeaders(); 263 } 264 265 266 @Override 267 public String toString() { 268 return "SendToMethodReturnValueHandler [annotationRequired=" + annotationRequired + "]"; 269 } 270 271 272 private static class DestinationVariablePlaceholderResolver implements PlaceholderResolver { 273 274 private final Map<String, String> vars; 275 276 public DestinationVariablePlaceholderResolver(Map<String, String> vars) { 277 this.vars = vars; 278 } 279 280 @Override 281 public String resolvePlaceholder(String placeholderName) { 282 return (this.vars != null ? this.vars.get(placeholderName) : null); 283 } 284 } 285 286}