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