am new to Spring Integration
my objective is passing message to one channel to another (chain process)
Channel1 ---> chennal2 --> chennal3 ---> chennal4.
(Msg1) (Msg2) (Msg3) (Msg4)
\ \ / /
\ \ / /
errorChennal (Msg5)
1. Msg1(EmployeeObject), Msg2 (DetailsObjet), Msg3(VerificationObject), Msg4(FinalObject), Msg5(ErrorObject)
Each channel payloads will have different Class Objects.
All the channel need to be communicated to "errorChennal" in case of Exception, validation error, etc.
Tried:
1. when I tried with #transformer am not able to communicate to "erroeChannel".
when I tried with #Router(header-value-router) Message transform not happing. Msg1 object is to all object
Question:
How do i route one channel to specific channel with message transformer?
FOUND ANSWER
Configuration:
<int:channel id="channel1"/>
<int:channel id="channel2"/>
<int:channel id="channel3"/>
<int:channel id="channel4"/>
<int:service-activator input-channel="channel1" ref="firstChannel" method="doProcess1" />
<int:service-activator input-channel="channel2" ref="secondChannel" method="doProcess2" />
<int:service-activator input-channel="channel3" ref="thirdChannel" method="doProcess3" />
<int:service-activator input-channel="channel4" ref="forthChannel" method="doProcess4" />
<int:service-activator input-channel="errorChannel" ref="errorHandlerChannel" method="doErrorProcess" />
Java Code:
public FirstChannel {
private Map<String, MessageChannel> msgChannels;
boolean isError = false;
#Autowired
public ScheduleParser(Map<String, MessageChannel> msgChannels) {
super();
this.msgChannels = msgChannels;
}
public void doprocess1(Message<?> message){
File file = (File) message.getPayload();
//business code
if(!isError)
//transforming the messae
msgChannels.get("channel2").send(new GenericMessage(EmployeeVO , headersMap));
else
msgChannels.get("errorChannel").send(new GenericMessage(ObjectVO , headersMap));
}
}
Same way Other channels Code will be
Channels aren't connected to each other they are connected to endpoints.
Transformers don't route, routers route.
You need something like
channel1->payload-type-router
type1Channel->...
type2Channel->...
...
possibly with a different transformer for each payload type downstream.
It's generally better to show the configuration you have tried rather than some abstraction like you have shown.
The error flow configuration depends on what starts your flow (gateway, poller, etc) - i.e. what is upstream of channel1.
EDIT
Your configuration is completely wrong. For example, you currently have two subscribers on channel1 - a header value router and service activator.
Messages arriving on channel1 will alternately go to one or the other - messages going to the service activator will never go to the router.
Without knowing the complete picture, I am guessing you need something like...
channel1->serviceActivator1->channel1toRouter->router(channel2 or failed)
channel2->serviceActivator2->channel2toRouter->router(channel2 or failed)
...
Related
I'm trying to send delayed messages on RabbitMQ with Spring AMQP.
I'm defining MessageProperties like this:
MessageProperties delayedMessageProperties = new MessageProperties();
delayedMessageProperties.setDelay(45000);
I'm defining the message which should be send in delay time like this:
org.springframework.amqp.core.Message amqpDelayedMessage = org.springframework.amqp.core.MessageBuilder.withBody(objectMapper.writeValueAsString(reversalMessage).getBytes())
.andProperties(reversalMessageProperties).build();
And then, If I send this message with RabbitTemplate, there is no problem. Message is being sent in defined delay time.
rabbitTemplate.convertSendAndReceiveAsType("delay-exchange",delayQueue, amqpDelayedMessage, new ParameterizedTypeReference<org.springframework.amqp.core.Message>() {
});
But I need to send this message asynchronously because I need not to block any other message in the system and to get more performance and if I use asyncRabbitTemplate, message is being delivered immediately. There is no delay.
asyncRabbitTemplate.convertSendAndReceiveAsType("delay-exchange",delayQueue, amqpDelayedMessage, new ParameterizedTypeReference<org.springframework.amqp.core.Message>() {
});
How can I obtain the delay with asnycRabbitTemplate?
This is probably a bug; please open an issue on GitHub.
The convertSendAndReceive() methods are not intended to send and receive raw Message objects.
In the case of the RabbitTemplate the conversion is skipped if the object is already a Message; there are some cases where this skip is not performed with the async template; please edit the question to show your template configuration.
However, since you are dealing with Message directly, don't use the convert... methods at all, simply use
public RabbitMessageFuture sendAndReceive(String exchange, String routingKey, Message message) {
I created an application that calls a gateway asynchronously using Splitter / Aggregator. In my concfiguration file, I invoke the process via InvestmentMessagingGateway that proceeds on calling the splitter. Every splitted message calls a service activator in parallel and pass it inn aggregator.
I placed an error channel in the InvestmentMessagingGateway and transform every failed message to pass to the aggregator as well.
I collect every successful and failed message in aggregator as a compilation for the response.
But when I tried to place an exception in one or more of the messages, I get an error in my aggregator,
Reply message received but the receiving thread has already received a reply.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="......."">
<context:component-scan base-package="com.api.investments"/>
<!--The gateway to be called in parallel-->
<gateway id="InvestmentGateway" service-interface="com.api.investments.gateways.InvestmentGateway"/>
<channel id="investmentDetailChannel"/>
<service-activator input-channel="investmentDetailChannel" ref="investmentService" method="getAccountPortfolio"/>
<!--Inbound gateway to invoke Splitter / Aggregator-->
<gateway id="InvestmentMessageGateway" service-interface="com.api.investments.gateways.InvestmentMessageGateway"
default-reply-channel="investmentAsyncReceiver" error-channel="investmentAsyncException"/>
<channel id="investmentAsyncSender"/>
<channel id="investmentAsyncReceiver"/>
<!-- Splitter for Invesment Details-->
<splitter input-channel="investmentAsyncSender" output-channel="investmentSplitChannel" id="investmentDetailsSplitter" ref="investmentComponentsSplitter" />
<channel id="investmentSplitChannel">
<queue />
</channel>
<!--Calls the Investment Gateway asynchronously using split messages ad send the response in aggregator-->
<service-activator input-channel="investmentSplitChannel" output-channel="investmentAggregateChannel" ref="investmentAsyncActivator" method="retrieveInvestmentDetailsAsync" requires-reply="true">
<poller receive-timeout="5000" task-executor="investmentExecutor" fixed-rate="50"/>
</service-activator>
<channel id="investmentAsyncException"/>
<!--Handles failed messages and pass it in aggregator-->
<transformer input-channel="investmentAsyncException" output-channel="investmentAggregateChannel" ref="invesmentErrorLogger" method="logError"/>
<!--Aggreggates successfull and failed messaged-->
<publish-subscribe-channel id="investmentAggregateChannel"/>
<aggregator input-channel="investmentAggregateChannel" output-channel="investmentAsyncReceiver" id="investmentAggregator"
ref="investmentComponentsAggregator" correlation-strategy="investmentComponentsCorrelationStrategy"
expire-groups-upon-completion="true"
send-partial-result-on-expiry="true" />
<task:executor id="investmentExecutor" pool-size="10-1000"
queue-capacity="5000"/>
</beans:beans>
I tried putting my error channel in the poller of the service activator and but the error is still the same but this time it didn't went to the aggregator.
I also tried putting a mid-gateway for the service activator like this
but the error became null.
<gateway id="InvestmentAsyncActivatorGateway" service-interface="com.api.investments.gateways.InvestmentAsyncActivatorGateway"
default-reply-channel="investmentAggregateChannel" error-channel="investmentAsyncException"/>
----UPDATE------
This is the transformer that handles every error message
#Component("invesmentErrorLogger")
public class InvesmentErrorLoggerImpl implements InvestmentErrorLogger {
private final Logger logger = LoggerFactory.getLogger(Application.class.getName());
/**
* handles all error messages in InvestmentMessageGateway
* Creates an error message and pass it in the aggregator channel
* #param invesmentMessageError
* #return errorMessage
*/
#Override
public Message<ErrorDetails> logError(Message<?> invesmentMessageError) {
if(invesmentMessageError.getPayload().getClass().equals(MessagingException.class)) {
MessagingException messageException = (MessagingException) invesmentMessageError.getPayload();
AccountPortfolioRequest failedMsgPayload = (AccountPortfolioRequest) messageException.getFailedMessage().getPayload();
String logError = "Exception occured in Account Number: " + failedMsgPayload.getiAccNo();
logger.error(logError);
ErrorDetails productErrorDetail = new ErrorDetails();
productErrorDetail.setCode(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO);
productErrorDetail.setMessage(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO_DESC + ". Problem occured in Account Number: " + failedMsgPayload.getiAccNo());
Message<ErrorDetails> errorMessage = MessageBuilder.withPayload(productErrorDetail)
.setHeaderIfAbsent(InvestmentInquiryConstants.INV_CORRELATION_STRATEGY, InvestmentInquiryConstants.INV_CORRELATION_STRATEGY_VALUE)
.build();
return errorMessage;
}
else if(invesmentMessageError.getPayload().getClass().equals(MessageDeliveryException.class)) {
MessageDeliveryException messageException = (MessageDeliveryException) invesmentMessageError.getPayload();
AccountPortfolioRequest failedMsgPayload = (AccountPortfolioRequest) messageException.getFailedMessage().getPayload();
String logError = "Exception occured in Account Number: " + failedMsgPayload.getiAccNo();
logger.error(logError);
ErrorDetails productErrorDetail = new ErrorDetails();
productErrorDetail.setCode(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO);
productErrorDetail.setMessage(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO_DESC + ". Problem occured in Account Number: " + failedMsgPayload.getiAccNo());
Message<ErrorDetails> errorMessage = MessageBuilder.withPayload(productErrorDetail)
.setHeaderIfAbsent(InvestmentInquiryConstants.INV_CORRELATION_STRATEGY, InvestmentInquiryConstants.INV_CORRELATION_STRATEGY_VALUE)
.build();
return errorMessage;
}
else {
Exception messageException = (Exception) invesmentMessageError.getPayload();
String logError = "Exception occured in Investment Gateway ";
logger.error(logError);
logger.equals(messageException.getMessage());
ErrorDetails productErrorDetail = new ErrorDetails();
productErrorDetail.setCode(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO);
productErrorDetail.setMessage(InvestmentAPIErrorMessages.SVC_ERR_INQACCNTPORTFOLIO_DESC + " " + messageException.getMessage());
Message<ErrorDetails> errorMessage = MessageBuilder.withPayload(productErrorDetail)
.setHeaderIfAbsent(InvestmentInquiryConstants.INV_CORRELATION_STRATEGY, InvestmentInquiryConstants.INV_CORRELATION_STRATEGY_VALUE)
.build();
return errorMessage;
}
}
}
Reply message received but the receiving thread has already received a reply.
As the error suggests, you can't send multiple replies (or errors) for a single request; it is strictly one reply per request.
You need another gateway between the splitter and the service.
The mid-flow gateway should have no service-interface so it uses the RequestReplyExchanger.
I am working on camel’s dynamic router to derive and dispatch to the relevant queue by referring http://camel.apache.org/dynamic-router.html
Following is the camel config xml:
<route id="dynamicRouter" autoStartup="true">
<from uri="vm://service1?concurrentConsumers=10&timeout=0" />
<choice>
<when>
<simple>${body.documentStatus} == 'SUCCESS' </simple>
<log message="routing to dynamic router" />
<dynamicRouter>
<!-- use a method call on a bean as dynamic router -->
<method ref="messageRouter" method="route" />
</dynamicRouter>
</when>
</choice>
</route>
Following is my dynamic router bean:
public class MessageRouter {
private static final String QUEUE_BROKER = "activemq";
private static final String DEFAULT_QUEUE = "queue";
/**
*
* #param obj
* message
* #return the dynamically generated queue name
*/
public String route(ServiceObject obj) {
RecordObject record = obj.getRecordObject();
if(record != null){
return QUEUE_BROKER + ":"
+ record.getId() + "." + DEFAULT_QUEUE;
}
return null;
}
}
I am able to en-queue messages in dynamically created queue but messages are getting enqueued infinitely. Any help in identifying what could be the reason would be of great help.
Thanks in advance!!!
Read the documentation about dynamic router at
http://camel.apache.org/dynamic-router.html
And see the beware box, it says the method must return null to signal to the dynamic router to break out.
So in your code above, then this code line
RecordObject record = obj.getRecordObject();
... the record object is never null and therefore the dynamic router keeps going forever.
If you only want this dynamic routing one time then use the dynamic receipient list eip instead, see
http://camel.apache.org/how-to-use-a-dynamic-uri-in-to.html
http://camel.apache.org/recipient-list.html
And your xml route is
<recipientList>
<!-- use a method call on a bean as dynamic router -->
<method ref="messageRouter" method="route" />
</recipientList>
everyone. I have an HTTP API for posting messages in a RabbitMQ broker and I need to implement the request-response pattern in order to receive the responses from the server. So I am something like a bridge between the clients and the server. I push the messages to the broker with specific routing-key and there is a Consumer for that messages, which is publishing back massages as response and my API must consume the response for every request. So the diagram is something like this:
So what I do is the following- For every HTTP session I create a temporary responseQueue(which is bound to the default exchange, with routing key the name of that queue), after that I set the replyTo header of the message to be the name of the response queue(where I will wait for the response) and also set the template replyQueue to that queue. Here is my code:
public void sendMessage(AbstractEvent objectToSend, final String routingKey) {
final Queue responseQueue = rabbitAdmin.declareQueue();
byte[] messageAsBytes = null;
try {
messageAsBytes = new ObjectMapper().writeValueAsBytes(objectToSend);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
MessageProperties properties = new MessageProperties();
properties.setHeader("ContentType", MessageBodyFormat.JSON);
properties.setReplyTo(responseQueue.getName());
requestTemplate.setReplyQueue(responseQueue);
Message message = new Message(messageAsBytes, properties);
Message receivedMessage = (Message)requestTemplate.convertSendAndReceive(routingKey, message);
}
So what is the problem: The message is sent, after that it is consumed by the Consumer and its response is correctly sent to the right queue, but for some reason it is not taken back in the convertSendAndReceived method and after the set timeout my receivedMessage is null. So I tried to do several things- I started to inspect the spring code(by the way it's a real nightmare to do that) and saw that is I don't declare the response queue it creates a temporal for me, and the replyTo header is set to the name of the queue(the same what I do). The result was the same- the receivedMessage is still null. After that I decided to use another template which uses the default exchange, because the responseQueue is bound to that exchange:
requestTemplate.send(routingKey, message);
Message receivedMessage = receivingTemplate.receive(responseQueue.getName());
The result was the same- the responseMessage is still null.
The versions of the amqp and rabbit are respectively 1.2.1 and 1.2.0. So I am sure that I miss something, but I don't know what is it, so if someone can help me I would be extremely grateful.
1> It's strange that RabbitTemplate uses doSendAndReceiveWithFixed if you provide the requestTemplate.setReplyQueue(responseQueue). Looks like it is false in your explanation.
2> To make it worked with fixed ReplyQueue you should configure a reply ListenerContainer:
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory);
container.setQueues(responseQueue);
container.setMessageListener(requestTemplate);
3> But the most important part here is around correlation. The RabbitTemplate.sendAndReceive populates correlationId message property, but the consumer side has to get deal with it, too: it's not enough just to send reply to the responseQueue, the reply message should has the same correlationId property. See here: how to send response from consumer to producer to the particular request using Spring AMQP?
BTW there is no reason to populate the Message manually: You can just simply support Jackson2JsonMessageConverter to the RabbitTemplate and it will convert your objectToSend to the JSON bytes automatically with appropriate headers.
I'm using Spring 4 websockets on Tomcat 8 and I have the following configuration:
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/notify">
<websocket:sockjs />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic" />
</websocket:message-broker>
My Spring controller has the following method:
#MessageMapping("/notify/{client}")
public void pushMessage(#DestinationVariable long client, String message) {
System.out.println("Send " + message + " to " + client);
template.convertAndSend("/topic/push/" + client, message);
}
So what I'm trying to do here is that if client 1 wants to send a message to client 2, he uses /app/notify/2. The Spring controller will then push the message to topic /topic/push/2.
I wrote the following code in my client:
var id = 1;
var sock = new SockJS('/project/notify');
var client = Stomp.over(sock);
client.connect({}, function() {
client.subscribe('/topic/push/' + id, function(message) {
console.log(message);
});
});
The connection works perfectly, /project is just the context root of my application.
I also have the following code in my client to send a message:
client.send('/app/notify/' + id, {}, "test");
Both variables (client and id) are accessible, I'm not getting any errors from this part of the code and I can see in my console that the message is actually sent:
>>> SEND
destination:/app/notify/1
content-length:4
test
However, the System.out.println() statement in my controller is never executed, so I assume there is something wrong with my controller mappings or I'm not using the destination endpoints correctly (I don't understand why I have to specify the application prefix here, but not when connecting to that endpoint).
It seems it's unable to map when using a simple String message as payload. When I wrap the message in an object, then it works just fine.
EDIT: As stated in the comments, Spring already comes with a message wrapper called the TextMessage class.