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}