/*
* Copyright 2002-2011 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.flow.handler;
import java.util.Collections;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.flow.FlowConstants;
import org.springframework.integration.flow.config.FlowUtils;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.ErrorMessage;
/**
* A MessageHandler for Handling Flow input and output. Sends messages on its
* input channel to the flow input channel and replies with the flow output (if
* there is one) to its output channel.
*
* Internally creates a subscriber to a PublishSubscribeChannel automatically
* created for the flow. Since all FlowMessageHandler instances subscribe to
* this channel, a unique flow conversationId is used to correlate flow input
* and output messages
*
* The output message contains a FLOW_OUTPUT_PORT_HEADER identifying which flow
* output port produced the message
* @see FlowUtils
*
* Error handling is done in a standard way. If the flow includes an error
* channel which is bound to an output port, the handler will send the
* ErrorMessage to the outputChannel. If the flow throws an exception, the
* handler will create an ErrorMessage and send it to the outputChannel
*
* @author David Turanski
*
*/
public class FlowMessageHandler extends AbstractReplyProducingMessageHandler {
private static Log log = LogFactory.getLog(FlowMessageHandler.class);
private final MessageChannel flowInputChannel;
private final SubscribableChannel flowOutputChannel;
private volatile MessageChannel errorChannel;
private final long timeout;
/**
*
* @param flowInputChannel the Flow input channel
* @param flowOutputChannel a PublishSubscribeChannel internally created and
* bridged to all flow output channels
* @param timeout the send timeout duration
*/
public FlowMessageHandler(MessageChannel flowInputChannel, SubscribableChannel flowOutputChannel, long timeout) {
this.flowInputChannel = flowInputChannel;
this.flowOutputChannel = flowOutputChannel;
this.timeout = timeout;
}
public void setErrorChannel(MessageChannel errorChannel) {
this.errorChannel = errorChannel;
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
UUID conversationId = requestMessage.getHeaders().getId();
Message<?> message = MessageBuilder.fromMessage(requestMessage).pushSequenceDetails(conversationId, 0, 0)
.build();
try {
ResponseMessageHandler responseMessageHandler = new ResponseMessageHandler(conversationId);
flowOutputChannel.subscribe(responseMessageHandler);
flowInputChannel.send(message, timeout);
flowOutputChannel.unsubscribe(responseMessageHandler);
return responseMessageHandler.getResponse();
}
catch (MessagingException me) {
log.error(me.getMessage(), me);
if (conversationId.equals(new IntegrationMessageHeaderAccessor(me.getFailedMessage()).getCorrelationId())) {
if (errorChannel != null) {
errorChannel.send(new ErrorMessage(me, Collections.singletonMap(
FlowConstants.FLOW_OUTPUT_PORT_HEADER,
(Object) FlowConstants.FLOW_HANDLER_EXCEPTION_HEADER_VALUE)));
}
}
else {
throw me;
}
}
return null;
}
/*
* Internal MessageHandler for the flow response
*/
private static class ResponseMessageHandler implements MessageHandler {
private final UUID conversationId;
private volatile Message<?> response;
public ResponseMessageHandler(UUID conversationId) {
this.conversationId = conversationId;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.messaging.MessageHandler#handleMessage
* (org.springframework.messaging.Message)
*/
@Override
public void handleMessage(Message<?> message) throws MessagingException {
Object correlationId = new IntegrationMessageHeaderAccessor(message).getCorrelationId();
if (log.isDebugEnabled()) {
log.debug("handling flow response message with conversation Id "
+ correlationId + ". Target conversation Id = " + this.conversationId
+ " match = " + conversationId.equals(correlationId));
}
if (conversationId.equals(correlationId)) {
this.response = MessageBuilder.fromMessage(message).popSequenceDetails().build();
if (log.isDebugEnabled()) {
log.debug("set flow response message " + this.response);
}
}
else {
/*
* Response from flow's ErrorChannel which is mapped to an
* output port.
*/
if (message instanceof ErrorMessage) {
MessagingException me = (MessagingException) message.getPayload();
if (conversationId.equals(new IntegrationMessageHeaderAccessor(me.getFailedMessage()).getCorrelationId())) {
this.response = message;
}
}
}
}
public Message<?> getResponse() {
return this.response;
}
}
}