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 org.apache.commons.logging.Log; 020 021import org.springframework.core.MethodParameter; 022import org.springframework.lang.Nullable; 023import org.springframework.messaging.Message; 024import org.springframework.messaging.MessageHeaders; 025import org.springframework.messaging.core.MessageSendingOperations; 026import org.springframework.messaging.handler.annotation.SendTo; 027import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; 028import org.springframework.messaging.simp.SimpLogging; 029import org.springframework.messaging.simp.SimpMessageHeaderAccessor; 030import org.springframework.messaging.simp.SimpMessageType; 031import org.springframework.messaging.simp.SimpMessagingTemplate; 032import org.springframework.messaging.simp.annotation.SendToUser; 033import org.springframework.messaging.simp.annotation.SubscribeMapping; 034import org.springframework.messaging.support.MessageHeaderInitializer; 035import org.springframework.util.Assert; 036 037/** 038 * {@code HandlerMethodReturnValueHandler} for replying directly to a 039 * subscription. It is supported on methods annotated with 040 * {@link org.springframework.messaging.simp.annotation.SubscribeMapping 041 * SubscribeMapping} such that the return value is treated as a response to be 042 * sent directly back on the session. This allows a client to implement 043 * a request-response pattern and use it for example to obtain some data upon 044 * initialization. 045 * 046 * <p>The value returned from the method is converted and turned into a 047 * {@link Message} that is then enriched with the sessionId, subscriptionId, and 048 * destination of the input message. 049 * 050 * <p><strong>Note:</strong> this default behavior for interpreting the return 051 * value from an {@code @SubscribeMapping} method can be overridden through use 052 * of the {@link SendTo} or {@link SendToUser} annotations in which case a 053 * message is prepared and sent to the broker instead. 054 * 055 * @author Rossen Stoyanchev 056 * @author Sebastien Deleuze 057 * @since 4.0 058 */ 059public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturnValueHandler { 060 061 private static final Log logger = SimpLogging.forLogName(SubscriptionMethodReturnValueHandler.class); 062 063 064 private final MessageSendingOperations<String> messagingTemplate; 065 066 @Nullable 067 private MessageHeaderInitializer headerInitializer; 068 069 070 /** 071 * Construct a new SubscriptionMethodReturnValueHandler. 072 * @param template a messaging template to send messages to, 073 * most likely the "clientOutboundChannel" (must not be {@code null}) 074 */ 075 public SubscriptionMethodReturnValueHandler(MessageSendingOperations<String> template) { 076 Assert.notNull(template, "messagingTemplate must not be null"); 077 this.messagingTemplate = template; 078 } 079 080 081 /** 082 * Configure a {@link MessageHeaderInitializer} to apply to the headers of all 083 * messages sent to the client outbound channel. 084 * <p>By default this property is not set. 085 */ 086 public void setHeaderInitializer(@Nullable MessageHeaderInitializer headerInitializer) { 087 this.headerInitializer = headerInitializer; 088 } 089 090 /** 091 * Return the configured header initializer. 092 */ 093 @Nullable 094 public MessageHeaderInitializer getHeaderInitializer() { 095 return this.headerInitializer; 096 } 097 098 099 @Override 100 public boolean supportsReturnType(MethodParameter returnType) { 101 return (returnType.hasMethodAnnotation(SubscribeMapping.class) && 102 !returnType.hasMethodAnnotation(SendTo.class) && 103 !returnType.hasMethodAnnotation(SendToUser.class)); 104 } 105 106 @Override 107 public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, Message<?> message) 108 throws Exception { 109 110 if (returnValue == null) { 111 return; 112 } 113 114 MessageHeaders headers = message.getHeaders(); 115 String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); 116 String subscriptionId = SimpMessageHeaderAccessor.getSubscriptionId(headers); 117 String destination = SimpMessageHeaderAccessor.getDestination(headers); 118 119 if (subscriptionId == null) { 120 throw new IllegalStateException("No simpSubscriptionId in " + message + 121 " returned by: " + returnType.getMethod()); 122 } 123 if (destination == null) { 124 throw new IllegalStateException("No simpDestination in " + message + 125 " returned by: " + returnType.getMethod()); 126 } 127 128 if (logger.isDebugEnabled()) { 129 logger.debug("Reply to @SubscribeMapping: " + returnValue); 130 } 131 MessageHeaders headersToSend = createHeaders(sessionId, subscriptionId, returnType); 132 this.messagingTemplate.convertAndSend(destination, returnValue, headersToSend); 133 } 134 135 private MessageHeaders createHeaders(@Nullable String sessionId, String subscriptionId, MethodParameter returnType) { 136 SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); 137 if (getHeaderInitializer() != null) { 138 getHeaderInitializer().initHeaders(accessor); 139 } 140 if (sessionId != null) { 141 accessor.setSessionId(sessionId); 142 } 143 accessor.setSubscriptionId(subscriptionId); 144 accessor.setHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER, returnType); 145 accessor.setLeaveMutable(true); 146 return accessor.getMessageHeaders(); 147 } 148 149}