My setup:
I have a message daemon using ActiveMQ which will consume JSON messages.
The publisher of JSON messages adds type header with value, for ex, com.example.Foo which is the type of the json message. I use this to transform json to pojo.
Spring config:
Once the message is received, these are the steps it goes through:
1. Transformer: Transforms json to pojo
2. Payload type router: Based on the type of the pojo, routes the pojo to appropriate service activator.
3. Service activator: Process the message.
<int:chain input-channel="transformerChannel">
<int:transformer id="jsonToPojoTransformer" ref="JsonToPojoTransformer" method="transform" />
<int:payload-type-router default-output-channel="defaultChannel">
<int:mapping type="com.example.Foo" channel="fooHandlerChannel"/>
<int:mapping type="com.example.Bar" channel="barHandlerChannel"/>
</int:payload-type-router>
</int:chain>
<int:service-activator input-channel="fooHandlerChannel" ref="fooHandler" method="onMessage"/>
<int:service-activator input-channel="barHandlerChannel" ref="barHandler" method="onMessage"/>
Service activator definition:
public class FooHandler {
public void onMessage(Foo foo) {...}
}
Problem:
I want to know how to access the message headers in the service activator. It seems like the service activator does not have access to the message headers since the transformer is returning a pojo.
Lets say the service activator is unable to call a down stream rest service for whatever reason. I want to skip processing this message now and I want to retry this message later. Or lets say there was an exception in processing this message. I want to retry processing this message after some delay. How do I accomplish this?
--edit--
Removed details to reduce question size as per Artem's comment.
Please, try do not make so long topics here in SO. It's hard to answer particular question if there are to many of them.
Absolutely not clear why you can't get access to header from your service activator method. You can accept the whole Message<>, and call its getHeaders(). You can use#Headersannotation on theMaparg to get headers from the message. You can use#Header` annotation to extract exactly particular header from the message.
Even if your transformer method returns just a POJO that doesn't mean that it isn't wrapped to the Message with the headers from requestMessage. If you need to return specific header alongside with your POJO, you should create Message yourself, using MessageBuilder and don't forget to copy requestMessage headers, just because transformer doesn't copy request headers if the whole message is returned.
You have to support TX on your JMS consumer, so that RuntimeException will lead to the rollback and, therefore, redelivery eventually. And you should ensure that all the flow is performed in the same thread. Otherwise TX is committed and message is acked on the broker. The same happens when you don't have transactions.
Related
Please take into consideration that the following example is from a SNS Topic with a SQS subscription, not a simple sqs message.
I'm currently using an SQSListener which receives a message from an SNS Topic with success:
#SqsListener("\${my-test-queue}")
fun featurePlacingListener(#NotificationMessage myJob: MyJob, #Headers headers: MessageHeaders) {
// Some code here
}
I would like to have an interceptor somehow so when there's a deserilization error I can log and send a notification.
I know this can be done If I forget using the #NotificationMessage, grab the Message part and deserialize manually but I would like to keep this clean.
Spring AOP doesn't work because the converter does the job before.
Something similar to a Controller advice or a nice AOP interceptor would be great
org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Cannot construct instance of `com.example.MyJob`, problem: Parameter specified as non-null is null: method com.example.MyJob.<init>, parameter myProperty
at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal
I just want to be able to detect when this happens.
The context: I have a message channel that does a post request to a service A and 2 interceptors - one that does some processing and another one that sends to another service B.
The problem: Where to add a feature toggle such that I can stop sending to A but still send to B? (also 1st interceptor does some processing that is needed before sending to B)
My approach was to add the feature toggle in the 2nd interceptor and return a null message object so that the messageChannel.send won't be executed. This works but also throws an error which is not desirable.
According to spring's message channel documentation:
The preSend of an interceptor is invoked before the Message is
actually sent to the channel. This allows for modification of the
Message if necessary. If this method returns {#code null} then the
actual send invocation will not occur.
Is there a way to silently kill the sending process or would there be a better approach?
Instead of using a ChannelInterceptor, try a Filter
Message filters are used to decide whether a Message should be passed along or dropped based on some criteria, such as a message header value or message content itself.
With a Filter you can specify your own implementation of a MessageSelector, which when returning false, will cause the Filter to discard the message.
package exampleCode;
import org.springframework.integration.core.MessageSelector;
import org.springframework.messaging.Message;
public class MySelector implements MessageSelector {
#Override
public boolean accept(Message<?> message) {
// If the message should continue through the filter, return true
// If the message should be blocked from continuing, return false
}
}
Then you can configure your selector to be used by a filter
<bean id="MySelector" class="exampleCode.MySelector" />
<int:filter input-channel="someChannel" ref="MySelector"
output-channel="someOtherChannel" />
Prior to version 3.0, invoking the send method on a PublishSubscribeChannel that had no subscribers returned false. When used in conjunction with a MessagingTemplate, a MessageDeliveryException was thrown. Starting with version 3.0, the behavior has changed such that a send is always considered successful if at least the minimum subscribers are present (and successfully handle the message). This behavior can be modified by setting the minSubscribers property, which defaults to 0.
Reference: https://docs.spring.io/spring-integration/docs/5.0.5.RELEASE/reference/html/messaging-channels-section.html
Also, I think you can implement Conditional Variables with Spring using #Conditional for the interceptors to execute for a given condition.
Reference: https://javapapers.com/spring/spring-conditional-annotation/#:~:text=Spring%20Boot%20module%20makes%20heavy,variables%2C%20irrespective%20of%20its%20value.
I have a trivial "Hello World" example workflow here where I want to expose an inbound-gateway web service that responds in plain text. I believe the way I route the response to myReplyChannel is incorrect.
<int:channel id="myRequestChannel"/>
<int:channel id="myReplyChannel"/>
<int-http:inbound-gateway id="myGateway"
path="/hi"
supported-methods="GET"
request-channel="myRequestChannel"
reply-channel="myReplyChannel"/>
<int:transformer input-channel="myRequestChannel"
output-channel="myReplyChannel"
expression="'Hello World!'"/>
This works when deployed, but when I first call the service I see this logged:
Adding {bridge:null} as a subscriber to the 'myReplyChannel' channel
Channel 'org.springframework.web.context.WebApplicationContext:myReplyChannel' has 1 subscriber(s).
started org.springframework.integration.endpoint.EventDrivenConsumer#4eef7503
Looks like Spring is adding in a subscriber for myReplyChannel at the last minute. I'd prefer to do it correctly myself.
Unit Test
I wrote a simple unit test to debug this..
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:hello.xml" })
public class HelloWorldTest {
#Autowired
private MessageChannel myRequestChannel;
#Test
public void test() {
myRequestChannel.send(MessageBuilder.withPayload("").build());
}
}
This errors out with:
org.springframework.messaging.MessageDeliveryException:
Dispatcher has no subscribers for channel
'org.springframework.context.support.GenericApplicationContext#64485a47.myReplyChannel'.
This reads to me like my configuration is wrong and here Spring isn't holding my hand.
Alternative Configuration:
I've tried just dropping myReplyChannel all together and it worked without anything in the logs.
<int:channel id="myRequestChannel"/>
<int-http:inbound-gateway id="myGateway"
path="/ok"
supported-methods="GET"
request-channel="myRequestChannel"/>
<int:transformer input-channel="myRequestChannel" expression="'OK'"/>
Is this the correct setup? If so, what is the reply-channel parameter for?
With this configuration, I get the following error in my unit test:
org.springframework.messaging.MessagingException:
org.springframework.messaging.core.DestinationResolutionException:
no output-channel or replyChannel header available
Adding {bridge:null} as a subscriber to the 'myReplyChannel' channel
.
debug this
There's nothing to "debug". This is just a DEBUG message from framework internals. Each request gets a dedicated replyChannelheader. Typically, you don't need a reply-channel on the gateway; the framework will automatically route to this request's reply channel header when it arrives at some component that has no output-channel (as you have found in your second test).
If you do specify a reply channel, the gateway creates a bridge internally so that any reply specifically sent there is bridged to the request's replyChannel header.
Typically, the only reason to ever specify a reply-channel is if you want do do something else with the reply (e.g. wire tap the channel to log the reply, or make the channel a publish-subscribe channel so you can send a copy of the reply somewhere else).
Your tests are failing because you are not populating the replyChannel header like the gateway does.
If you want to emulate the HTTP gateway in your test code, use a messaging gateway, or simply use a MessagingTemplate.convertSendAndReceive() - either one will correctly set up the replyChannel header in the request message.
Alternatively, use:
myRequestChannel.send(MessageBuilder.withPayload("")
.setReplyChannel(new QueueChannel())
.build());
Each request needs its own reply channel header so we know how to route the reply to the right requesting thread.
I've read that if exception is thrown in the flow the first thing the framework will do is check message header for error-channel property. Is it always the case?
In my particular case I'm assigning a custom error-channel to a message header and yet the message seems to get propagated up the stream to the nearest error-handler/error-channel.
<int:chain id="buildAggregatedResponseChain" input-channel="aggregatedResultChannel"
output-channel="sendAggregatedChannel">
<int:header-enricher>
<int:error-channel ref="myErrorChannel"/>
</int:header-enricher>
<int:service-activator ref="service" method="doSomething"/>
</int:chain>
I explicitly throw an exception inside doSomething but the exception never ends up in myErrorChannel. Instead, it is "propagated" to the nearest ErrorHandler up the stream or to the error-channel specified up the stream for int-mail:imap-idle-channel-adapter(tried several different flows).
What do I miss? Maybe someone can outline the main principal of error handling/error propagation(for example when talking about several transactions, etc)? There is some information out there, but it is quite scattered and not systematic.
It depends on the upstream flow; if there's an async handoff the header is consulted; otherwise, the exception is thrown back to the inbound endpoint.
In general, I would advise against modifying framework headers such as errorChannel. Instead put an error-channel on the inbound endpoint (such as your imap idle adapter) and handle the errors on that flow.
Modifying the headers directly is rarely needed. If you wish to insert different error-handling mid-flow then you can insert a messaging gateway...
<int:service activator ... ref="gw" />
<int:gateway id="gw" default-request-channel="..."
error-channel="midFlowErrorChannel" />
If the downstream flow (from the gateway) returns no result on success, then be sure to add a default reply timeout of 0 (or use a custom service interface with a method that returns void).
I have created a Route that looks roughly like this:
#Override
public void configure() throws Exception {
from("direct:twitter")
.from("twitter://timeline/user?type=direct&user=" + this.uri)
.choice()
.when(body().isInstanceOf(Status.class))
.process(new MyTwitterProcessor())
.convertBodyTo(MyClass.class)
.to("log:do something")
.endChoice()
.to("log:mi amigo");
}
Calling this route directly from producerTemplate.requestBody("direct:twitter", object), I expected to receive a list of MyClass.class instances. Instead, it is returning the object I sent in the requestBody method call.
Based on log statements "log:do something" I can see that Status objects are being returned- the request and response from twitter are clearly occuring.
I would like to understand why my route configuration is returning the object I send it, rather than the Status object results from twitter. I have written two other routes for Facebook posts and an RSS feed. They follow a similar pattern and return the response objects, rather than the request I sent.
I would also like to know what I can do to change the behavior so that producerTemplate.requestBody(...) returns a list of twitter messages.
Thank you for your time.
Use the pollEnrich component to obtain additional data:
from("direct:twitter")
.pollEnrich("twitter://timeline/user?type=direct&user=" + this.uri)
.choice()
...
Alternatively, you may just use following route that is automatically started:
from("twitter://timeline/user?type=direct&user=" + this.uri)
.choice()
...
Note, that the twitter component creates one route exchange per returned object and not a list.