/* * Copyright 2014-2017 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. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.integration.handler; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.reactivestreams.Publisher; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.routingslip.RoutingSlipRouteStrategy; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessagingException; import org.springframework.messaging.core.DestinationResolutionException; import org.springframework.messaging.support.ErrorMessage; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.util.concurrent.SettableListenableFuture; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * The base {@link AbstractMessageHandler} implementation for the {@link MessageProducer}. * * @author David Liu * @author Artem Bilan * @author Gary Russell * * since 4.1 */ public abstract class AbstractMessageProducingHandler extends AbstractMessageHandler implements MessageProducer { protected final MessagingTemplate messagingTemplate = new MessagingTemplate(); private volatile MessageChannel outputChannel; private volatile String outputChannelName; private volatile boolean async; /** * Set the timeout for sending reply Messages. * @param sendTimeout The send timeout. */ public void setSendTimeout(long sendTimeout) { this.messagingTemplate.setSendTimeout(sendTimeout); } @Override public void setOutputChannel(MessageChannel outputChannel) { this.outputChannel = outputChannel; } public void setOutputChannelName(String outputChannelName) { Assert.hasText(outputChannelName, "'outputChannelName' must not be empty"); this.outputChannelName = outputChannelName; //NOSONAR (inconsistent sync) } /** * Allow async replies. If the handler reply is a {@link ListenableFuture}, send * the output when it is satisfied rather than sending the future as the result. * Ignored for return types other than {@link ListenableFuture}. * @param async true to allow. * @since 4.3 */ public final void setAsync(boolean async) { this.async = async; } /** * @see #setAsync(boolean) * @return true if this handler supports async replies. * @since 4.3 */ protected boolean isAsync() { return this.async; } @Override protected void onInit() throws Exception { super.onInit(); Assert.state(!(this.outputChannelName != null && this.outputChannel != null), //NOSONAR (inconsistent sync) "'outputChannelName' and 'outputChannel' are mutually exclusive."); if (getBeanFactory() != null) { this.messagingTemplate.setBeanFactory(getBeanFactory()); } this.messagingTemplate.setDestinationResolver(getChannelResolver()); } @Override public MessageChannel getOutputChannel() { if (this.outputChannelName != null) { synchronized (this) { if (this.outputChannelName != null) { this.outputChannel = getChannelResolver().resolveDestination(this.outputChannelName); this.outputChannelName = null; } } } return this.outputChannel; } protected void sendOutputs(Object result, Message<?> requestMessage) { if (result instanceof Iterable<?> && shouldSplitOutput((Iterable<?>) result)) { for (Object o : (Iterable<?>) result) { this.produceOutput(o, requestMessage); } } else if (result != null) { this.produceOutput(result, requestMessage); } } protected boolean shouldSplitOutput(Iterable<?> reply) { for (Object next : reply) { if (next instanceof Message<?> || next instanceof AbstractIntegrationMessageBuilder<?>) { return true; } } return false; } @SuppressWarnings("unchecked") protected void produceOutput(Object reply, final Message<?> requestMessage) { final MessageHeaders requestHeaders = requestMessage.getHeaders(); Object replyChannel = null; if (getOutputChannel() == null) { Map<?, ?> routingSlipHeader = requestHeaders.get(IntegrationMessageHeaderAccessor.ROUTING_SLIP, Map.class); if (routingSlipHeader != null) { Assert.isTrue(routingSlipHeader.size() == 1, "The RoutingSlip header value must be a SingletonMap"); Object key = routingSlipHeader.keySet().iterator().next(); Object value = routingSlipHeader.values().iterator().next(); Assert.isInstanceOf(List.class, key, "The RoutingSlip key must be List"); Assert.isInstanceOf(Integer.class, value, "The RoutingSlip value must be Integer"); List<?> routingSlip = (List<?>) key; AtomicInteger routingSlipIndex = new AtomicInteger((Integer) value); replyChannel = getOutputChannelFromRoutingSlip(reply, requestMessage, routingSlip, routingSlipIndex); if (replyChannel != null) { //TODO Migrate to the SF MessageBuilder AbstractIntegrationMessageBuilder<?> builder = null; if (reply instanceof Message) { builder = this.getMessageBuilderFactory().fromMessage((Message<?>) reply); } else if (reply instanceof AbstractIntegrationMessageBuilder) { builder = (AbstractIntegrationMessageBuilder<?>) reply; } else { builder = this.getMessageBuilderFactory().withPayload(reply); } builder.setHeader(IntegrationMessageHeaderAccessor.ROUTING_SLIP, Collections.singletonMap(routingSlip, routingSlipIndex.get())); reply = builder; } } if (replyChannel == null) { replyChannel = requestHeaders.getReplyChannel(); if (replyChannel == null && reply instanceof Message) { replyChannel = ((Message<?>) reply).getHeaders().getReplyChannel(); } } } if (this.async && (reply instanceof ListenableFuture<?> || reply instanceof Publisher<?>)) { if (reply instanceof ListenableFuture<?> || !(getOutputChannel() instanceof ReactiveStreamsSubscribableChannel)) { ListenableFuture<?> future; if (reply instanceof ListenableFuture<?>) { future = (ListenableFuture<?>) reply; } else { SettableListenableFuture<Object> settableListenableFuture = new SettableListenableFuture<>(); Mono.from((Publisher<?>) reply) .subscribe(settableListenableFuture::set, settableListenableFuture::setException); future = settableListenableFuture; } Object theReplyChannel = replyChannel; future.addCallback(new ListenableFutureCallback<Object>() { @Override public void onSuccess(Object result) { Message<?> replyMessage = null; try { replyMessage = createOutputMessage(result, requestHeaders); sendOutput(replyMessage, theReplyChannel, false); } catch (Exception e) { Exception exceptionToLogAndSend = e; if (!(e instanceof MessagingException)) { exceptionToLogAndSend = new MessageHandlingException(requestMessage, e); if (replyMessage != null) { exceptionToLogAndSend = new MessagingException(replyMessage, exceptionToLogAndSend); } } logger.error("Failed to send async reply: " + result.toString(), exceptionToLogAndSend); onFailure(exceptionToLogAndSend); } } @Override public void onFailure(Throwable ex) { sendErrorMessage(requestMessage, ex); } }); } else { ((ReactiveStreamsSubscribableChannel) getOutputChannel()) .subscribeTo( Flux.from((Publisher<?>) reply) .map(result -> createOutputMessage(result, requestHeaders))); } } else { sendOutput(createOutputMessage(reply, requestHeaders), replyChannel, false); } } private Object getOutputChannelFromRoutingSlip(Object reply, Message<?> requestMessage, List<?> routingSlip, AtomicInteger routingSlipIndex) { if (routingSlipIndex.get() >= routingSlip.size()) { return null; } Object path = routingSlip.get(routingSlipIndex.get()); Object routingSlipPathValue = null; if (path instanceof String) { routingSlipPathValue = getBeanFactory().getBean((String) path); } else if (path instanceof RoutingSlipRouteStrategy) { routingSlipPathValue = path; } else { throw new IllegalArgumentException("The RoutingSlip 'path' can be of " + "String or RoutingSlipRouteStrategy type, but got: " + path.getClass()); } if (routingSlipPathValue instanceof MessageChannel) { routingSlipIndex.incrementAndGet(); return routingSlipPathValue; } else { Object nextPath = ((RoutingSlipRouteStrategy) routingSlipPathValue).getNextPath(requestMessage, reply); if (nextPath != null && (!(nextPath instanceof String) || StringUtils.hasText((String) nextPath))) { return nextPath; } else { routingSlipIndex.incrementAndGet(); return getOutputChannelFromRoutingSlip(reply, requestMessage, routingSlip, routingSlipIndex); } } } protected Message<?> createOutputMessage(Object output, MessageHeaders requestHeaders) { AbstractIntegrationMessageBuilder<?> builder = null; if (output instanceof Message<?>) { if (!this.shouldCopyRequestHeaders()) { return (Message<?>) output; } builder = this.getMessageBuilderFactory().fromMessage((Message<?>) output); } else if (output instanceof AbstractIntegrationMessageBuilder) { builder = (AbstractIntegrationMessageBuilder<?>) output; } else { builder = this.getMessageBuilderFactory().withPayload(output); } if (this.shouldCopyRequestHeaders()) { builder.copyHeadersIfAbsent(requestHeaders); } return builder.build(); } /** * Send an output Message. The 'replyChannel' will be considered only if this handler's * 'outputChannel' is <code>null</code>. In that case, the 'replyChannel' value must not also be * <code>null</code>, and it must be an instance of either String or {@link MessageChannel}. * @param output the output object to send * @param replyChannel the 'replyChannel' value from the original request * @param useArgChannel - use the replyChannel argument (must not be null), not * the configured output channel. */ protected void sendOutput(Object output, Object replyChannel, boolean useArgChannel) { MessageChannel outputChannel = getOutputChannel(); if (!useArgChannel && outputChannel != null) { replyChannel = outputChannel; } if (replyChannel == null) { throw new DestinationResolutionException("no output-channel or replyChannel header available"); } if (replyChannel instanceof MessageChannel) { if (output instanceof Message<?>) { this.messagingTemplate.send((MessageChannel) replyChannel, (Message<?>) output); } else { this.messagingTemplate.convertAndSend((MessageChannel) replyChannel, output); } } else if (replyChannel instanceof String) { if (output instanceof Message<?>) { this.messagingTemplate.send((String) replyChannel, (Message<?>) output); } else { this.messagingTemplate.convertAndSend((String) replyChannel, output); } } else { throw new MessagingException("replyChannel must be a MessageChannel or String"); } } /** * Subclasses may override this. True by default. * @return true if the request headers should be copied. */ protected boolean shouldCopyRequestHeaders() { return true; } protected void sendErrorMessage(final Message<?> requestMessage, Throwable ex) { Object errorChannel = resolveErrorChannel(requestMessage.getHeaders()); Throwable result = ex; if (!(ex instanceof MessagingException)) { result = new MessageHandlingException(requestMessage, ex); } if (errorChannel == null) { logger.error("Async exception received and no 'errorChannel' header exists and no default " + "'errorChannel' found", result); } else { try { sendOutput(new ErrorMessage(result), errorChannel, true); } catch (Exception e) { Exception exceptionToLog = e; if (!(e instanceof MessagingException)) { exceptionToLog = new MessageHandlingException(requestMessage, e); } logger.error("Failed to send async reply", exceptionToLog); } } } protected Object resolveErrorChannel(final MessageHeaders requestHeaders) { Object errorChannel = requestHeaders.getErrorChannel(); if (errorChannel == null) { try { errorChannel = getChannelResolver().resolveDestination("errorChannel"); } catch (DestinationResolutionException e) { // ignore } } return errorChannel; } }