Spring Integration DSL JmsMessageDrivenChannelAdapter infinite retry after the error handler processing - spring-integration

Background:
We have a Spring Integration adapter written in Spring XML config as shown below. It is working perfectly in all the scenarios w.r.to error handling. All the thing, error handling does is to write the error message to a queue. Now we have a need to covert this xml config to DSL, we have changed this using the below code.
Problem:
Whenever an error happens inside 'inputChannel' chain, we wanted the error handling to do some inspection and write the error to error queue and do not retry the Payload. Spring XML is doing exactly what is needed but when we change it to DSL after placing the error message to error queue, the payload is written back to the input queue and the error message from the queue disappears and this goes in a loop that never ends.
Analysis we did:
There is no error happening after the error message is written to error queue and DSL adapter config doesn't have anything as such to process.
Any help/direction to solve this is much appreciated.
Working Spring XML adapter:
<int-jms:message-driven-channel-adapter
channel="inputChannel" container="jmsContainer" extract-payload="true" />
<beans:bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<beans:property name="connectionFactory" ref="connectionFactory" />
<beans:property name="destinationName" value="Queue.test" />
<beans:property name="errorHandler" ref="errorHandler" />
</beans:bean>
Problematic adapter in DSL:
private JmsMessageDrivenChannelAdapter MessageDrivenChannelAdapter(
String destinationName, String key) throws Exception {
JmsMessageDrivenChannelAdapter channelAdapter = Jms
.messageDriverChannelAdapter(connectionFactory)
.outputChannel(inputChannel)
.configureListenerContainer(
c -> c.errorHandler(errorHandler))
.destination(destinationName)
.setHeaderMapper(new HeaderMapper(getChannelHeaders(key)))
.get();
return channelAdapter;
}

There are some questions:
You don't show how you use that MessageDrivenChannelAdapter().
You should share the DEBUG logs to demonstrate how messages should travel and how don't.
If I were you, I'd convert that XML to this Java DSL:
#Bean
public DefaultMessageListenerContainer jmsContainer() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(jmsConnectionFactory());
container.setDestinationName("Queue.test");
container.setErrorHandler(errorHandler);
return container;
}
#Bean
public IntegrationFlow myJmsFlow() {
return IntegrationFlows.from(
Jms.messageDrivenChannelAdapter(jmsContainer())
.extractPayload(true))
.channel(inputChannel)
.get();
}
The main point there is jmsContainer bean as it is in your XML config.
And pay attention how I use Jms.messageDrivenChannelAdapter() - from the IntegrationFlows.from() and without get() call.
If you are going to use that MessageDrivenChannelAdapter() method, it must be public and #Bean, otherwise all the internals of the IntegrationComponentSpec are not going to work because they are lost after .get() call.

Related

Spring Integration : How to intercept ALL spring integration component and fetch attribute values to log

I need to intercept ALL the Spring Integration components at runtime and should be able to fetch the attribute values in order to log a meaningful message.
For example:
<int-http:outbound-gateway url="someURL" http-method="GET"
request-channel="channel1"
expected-response-type="com.example.Test"
message-converters="customMessageConverters">
<int-http:uri-variable name="testId" expression="headers.testId"/>
</int-http:outbound-gateway>
In the example above, I need to intercept int-http:outbound-gateway and capture the value for url, request-channel and expected-response-type. We need to do this for all http outbound gateway.
Similarly, for all other components like int-http:inbound-gateway, int-http:inbound-channel-adapter, int:transformer, int:header-enricher, int:chain, int:router, etc.
I have tried creating a custom class implementing BeanPostProcessor - postProcessAfterInitialization method. Checked for the bean name to be matching with the component, and tried to retrieve all the details but the beans are created and this method is called at the server startup itself. My requirement is to capture the flow as and when the user navigates and any particular route is being called. Also I am not able to find Java class name for all the component apart from the below. Still finding for the rest.
org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway for int-http:inbound-gateway, org.springframework.integration.http.outbound.AbstractHttpRequestExecutingMessageHandler for int-http:outbound-gateway
Update:
I have tried the below but cant see any extra output in logs with respect to message history. Is anything missing in the above code?
<int:message-history />
<int:logging-channel-adapter id="logging"
log-full-message="true" logger-name="message.history" level="DEBUG"/>
<int:wire-tap pattern="*" order="3" channel="logging" />
or
<int:message-history />
<int:logging-channel-adapter id="logger"
log-full-message="true" logger-name="message.history" level="DEBUG"/>
<int:channel id="wiretapChannel">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
Also, I am trying to inject LogMessage into wire-tap inorder to perform some additional tasks from MessageHistory data. But the control doesn't enter handleMessage method. Please help.
<bean id="logMessage" class="com.logging.LogMessage"/>
<int:service-activator input-channel="wiretapChannel" ref="logMessage" method="handleMessage"></int:service-activator>
public class LogMessage {
public void handleMessage(org.springframework.messaging.Message<?> message) throws MessagingException {
MessageHistory history = MessageHistory.read(message);
for (int i = 0; i < history.size(); i++) {
Properties properties = history.get(i);
getLogger().info("history: " + properties.get("name"));
}
}
}
Well, it's not so standard task, especially for the reading properties you would like to print. More over many of them are based on the SpEL expressions and the actual value depends on the request message.
There is though a component which is very close what you would like to get. It is called Message History.
All what you need is <int:message-history/> - and all the Spring Integration components will be tracked and will store they point into the MessageHistory.HEADER_NAME to show the whole path of the message over integration flow.
In addition I usually also use something like this:
<wire-tap channel="logger"/>
<logging-channel-adapter id="logger" log-full-message="true" logger-name="message.history"/>
To intercept all the channels in the application and log messages with their message history.
You may create your own POJO subscriber (<service-activator>) for the global <wire-tap> and perform some smart logic to extract a MessageHistory from the message: MessageHistory.read(Message<?>). Such a MessageHistory is a List<Properties> where, actually you can cast that Properties in the MessageHistory.Entry class and walk over its properties:
public String getName() {
return this.getProperty(NAME_PROPERTY);
}
public String getType() {
return this.getProperty(TYPE_PROPERTY);
}
public String getTimestamp() {
return this.getProperty(TIMESTAMP_PROPERTY);
}
With the name you can go to the BeanFactory to get the real component instance and already there try to extract required properties for your purpose, but again: not all of them are going to be available just because...
You also can consult with the Integration Graph for possible public properties of the IntegrationNode implementations.

No annotated method found, falling back to SequenceSizeReleaseStrategy

I tried to create a single bean which combined the release strategy method and the aggregator method:
public class POJOSingleAggregatorStrategy{
#Aggregator
public Message spliceMessage(List<Message<?>> messages) {
...
}
#ReleaseStrategy
public boolean canRelease(MessageGroup messageGroup) {
...
}
}
<channel id="inputChannel"/>
<channel id="outputChannel"/>
<bean id="aggregatorStrategyBean" class="sample.POJOSingleAggregatorStrategy"/>
<aggregator id="completelyDefinedAggregator" input-channel="inputChannel" output-channel="outputChannel" send-partial-result-on-expiry="false" ref="aggregatorStrategyBean" method="spliceMessage"/>
Without config release-strategy-method, the program works well but it repors a warning:
No annotated method found; falling back to SequenceSizeReleaseStrategy, target:sample.POJOSingleAggregatorStrategy, methodName:null
After configured release-strategy-method="canRelease", the program doesn't work and it reports an exception: java.lang.IllegalStateException: Failed to process message list
Anybody knows how to avoid this warning? (Except for rewrite ReleaseStrategyFactoryBean)
Strange indeed; we have a test case here and here that does the same thing and it works fine.
If you can post a simple test case that reproduces the issue, we can take a look.
You also need to provide the full stack trace for the IllegalStateException.

Message not available at end of Aggregator

I have built a Spring Integration application and transferred some messages around and tried to bring them together with an Aggregator. The application reaches the Aggregator but does not deliver exactly what I want specifically I do not release the group and move onto the next step.
My problem however is my aggregator doesn't have the original message (from before the Splitter). My aggregator is defined as follows
<int:aggregator input-channel="deirBoxProcessorToAggregatorChannel"
ref="loggingAggregator" method="logAggregation"
output-channel="aggregatorToTransformer"
expire-groups-upon-completion="true"/>
And the code inside it is as follows..
public class LoggingAggregator {
private static final Logger LOGGER = Logger.getLogger(LoggingAggregator.class);
public void logAggregation(Message<File> message) {
LOGGER.info("Have aggregated messsages. Will archive");
}
My message in that method, although it enters it, is always null.
Application Context/XML Spring Integration definition
<int:splitter input-channel="transformerToSplitterChannel"
ref="fileToMessageSplitter"
output-channel="shippedSplitterToRouterChannel"
method="split" apply-sequence="true"/>
<!-- Now use a router to determine which Message builder these messages are sent onto -->
<int:router input-channel="shippedSplitterToRouterChannel"
ref="shippedToTypeRouter" />
<int:transformer input-channel="deirShippedBoxToTransformerChannel"
ref="shippedBoxTransformer" method="transform" output-
channel="deirShippedTransformerToProcessorChannel"/>
<int:service-activator id="wellFormedShippedBoxProcess"
input-channel="deirShippedTransformerToProcessorChannel"
output-channel="deirBoxProcessorToAggregatorChannel"
ref="deirShippedFileProcessor" method="processBox" />
<int:service-activator id="malformedShippedBoxProcess"
input-channel="deirMalformedShippedTransformerToProcessorChannel"
output-channel="deirBoxProcessorToAggregatorChannel"
ref="deirShippedFileProcessor"
method="processMalformedBox" />
<int:aggregator input-channel="deirBoxProcessorToAggregatorChannel"
ref="loggingAggregator" method="logAggregation"
output-channel="aggregatorToTransformer"
expire-groups-upon-completion="true"/>
<int:transformer expression="headers.file_originalFile"
input-channel="aggregatorToTransformer"
output-channel="transformerToArchiver" />
<int-file:outbound-channel-adapter id="deirArchiver"
channel="transformerToArchiver"
directory="${dataexhange.springintg.refactor.archive.dir}"
delete-source-files="true"/>
The process gets all the way to the Aggregator but does not seem to make it past to the Transformer or OutboundChannelAdapter archiver.
Thank you in advance.
Your LoggingAggregator isn't correct. I recommend you to read the Reference Manual.
Your logAggregation method should be like this:
public File logAggregation(List<String> lines) {
LOGGER.info("Have aggregated messsages. Will archive");
// Create Files from lines
return file;
}
It is a main method of Aggregator: to get a list of objects and return one object.
Artem's answer is correct. I mistakenly thought that the objects I returned to the aggregator would be of type that were sent off by the splitter. You can follow how through debugging I came to that realisation in the comments to Artem's answer.
I did see somewhere, probably in the manual you can in fact return a type that can be cast from the channel that feeds into the aggregator.
With that understanding I could in fact return Object, and cast back up to the required type for use in the logging object I would use either subsequent to or as part of the aggregator.

Spring Integration: getting the name of the underlying Queue from a Message or MessageChannel

Given a MessageChannel or Message object, how is it possible to get from one of them the name of the underlying JMS Queue which the message was received on ?
Here is the scenario:
Several jms:message-driven-channel-adapter instances are defined in the xml. The destination-name of each adapter uses SEL to receive from different queues. This SEL is dynamic, and is not possible to know these queue names ahead of time. All channel adapters output to the same internal Spring Integration channel.
I want to add the actual underlying queue name which the message was received on to the header of the message.
The idea is to setup a ChannelInterceptor for either the channel-adapters or the internal channel. The postReceive() method has both the Message and MessageChannel as arguments. Using either of these, is it possible to get the name of the underlying Queue name which the message came in on?
Thanks
Looks like you need to extend a bit DefaultJmsHeaderMapper:
class DestinationJmsHeaderMapper extends DefaultJmsHeaderMapper {
public Map<String, Object> toHeaders(javax.jms.Message jmsMessage) {
Map<String, Object> headers = super.toHeaders(jmsMessage);
headers.put("JMS_DESTINATION", ((Queue) jmsMessage.getJMSDestination()).getQueueName());
}
}
And inject it to your <jms:message-driven-channel-adapter>s
This is how we did it:
<int:header-enricher>
<int:header name="JMS_DESTINATION" expression="payload.JMSDestination.queueName"/>
</int:header-enricher>
It requires extract-payload="false" in your <jms:message-driven-channel-adapter>.
P.S. The answer of Artem is missing the return statement (I do not have enough reputations to comment).

Mule security filter breaking flow

I'm completely at a loss with my knowledge gap.
Mule 3.3.1 CE. I must use that version for now.
I have a flow which works fine until I try to use a security filter with valid credentials.
The Mule code follows. It won't make much business sense as I've pared it down to the minimum that produces the problem. Normally the outbound call is in a separate flow, but I pulled it into the Main flow for the example.
If I have the security filter on the inbound endpoint commented out and execute this, I get the expected response of the logger message and the "foo" return payload.
<https:connector name="HTTPSConnector" validateConnections="true" sendBufferSize="0" receiveBufferSize="0" receiveBacklog="0" clientSoTimeout="10000" serverSoTimeout="10000" socketSoLinger="0" doc:name="HTTP\HTTPS">
<https:tls-key-store path="/opt/eai/common/keystore/EAIKeystore.jks" keyPassword="${key.password}" storePassword="${store.password}"/>
</https:connector>
<spring:beans>
<ss:authentication-manager alias="authManager">
<ss:authentication-provider>
<ss:user-service id="userService">
<ss:user name="PortalUser" password="password" authorities="ROLE_USER"/>
</ss:user-service>
</ss:authentication-provider>
</ss:authentication-manager>
</spring:beans>
<mule-ss:security-manager>
<mule-ss:delegate-security-provider name="memory-provider" delegate-ref="authManager"/>
</mule-ss:security-manager>
<flow name="Main" doc:name="Main">
<https:inbound-endpoint exchange-pattern="request-response" host="localhost" port="10029" path="sites/r.v1" mimeType="text/xml" encoding="UTF-8" connector-ref="HTTPSConnector">
<mule-ss:http-security-filter realm="mule-realm"/>
</https:inbound-endpoint>
<custom-transformer class="com.ca.eai.esb.transformer.site.StrategySplittingTransformer" doc:name="Split"/>
<collection-splitter/>
<https:outbound-endpoint exchange-pattern="request-response"
address="${https.outbound.account.sap-nameaddress}"
connector-ref="HTTPSConnector"
mimeType="text/xml" responseTimeout="${https.outbound.timeout}"/>
<logger level="INFO" message="GOT HERE"/>
<set-payload value="foo"/>
</flow>
The custom transformer is also pared down to the minimum:
#Override
public Object transformMessage( MuleMessage message, String outputEncoding )
{
MuleMessageCollection collection = new DefaultMessageCollection( message.getMuleContext() );
collection.addMessage( message );
return collection;
}
If I uncomment the security filter and pass in bad credentials, I get the expected security exception.
If, however, I pass in valid credentials, I get an exception that it can't serialize.
Root Exception stack trace:
java.io.NotSerializableException: org.apache.commons.httpclient.ContentLengthInputStream
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
at java.util.concurrent.CopyOnWriteArrayList.writeObject(CopyOnWriteArrayList.java:857)
+ 3 more (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)
Can someone tell me why adding a security filter causes this?
This is a bug that has been fixed in 3.4.x.
See this answer https://stackoverflow.com/a/17930063/387927 and the comments below it.

Resources