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.
Related
I need to pass the parameter in the method called via service-activator. I am able to successfully do this with the help of header-enricher. Below is the working code snippet.
<int:chain input-channel="inChannel">
<int:header-enricher>
<int:header name="routeName" value="TestRoute" />
</int:header-enricher>
<int:service-activator ref="customLoggingRoute"
method="logRoute">
</int:service-activator>
</int:chain>
public Message logRoute(Message m, #Header("routeName") String routeName) {
System.out.println("Inside route: " + routeName);
return m;
}
But I dont want to add anything to header. Is there any alternative by which we can accomplish the same thing without header-enricher.
No, there is no. The Spring Integration contract for invokers is Messaging. So, when we have only a message in between, there is nothing more we can utilize. You can have a complex payload object and transfer data through it, or move your data into headers.
Of course you can consider to use a ThreadLocal since we are in Java and as long as your flow is in the same thread, but this is going to be slightly overhead.
I think you need to start from the theory of the Messaging and come back to us when you understand all those restrictions.
https://www.enterpriseintegrationpatterns.com/
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.
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.
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).
I have two different services running on a web server. Both the services have an operation named 'xyz', with the following arguments.
Service 1:
Public String xyx(Student object) {}
Service 2:
public String xyz(Employee object){}
Now i have a client which will invoke the operation of one of these services based on the message that it receives. The message will be received as a camel exchange. So i need to identify the type of the message and then invoke the appropriate service.
How do i identify the original type of the message that is received as a camel exchange.
Thanks.
Or you can do something like this:
from("foo:incommingroute")
.choice()
.when(simple("${body} is 'java.lang.String'"))
.to("webservice:Student")
.when(simple("${body} is 'foo.bar.Employee'"))
.to("webservice:Employee")
.otherwise()
.to("jms:Deadletter")
.end();
Try exchange.getIn().getBody() instanceof Student
I would set the a value in the header to indicate which service it is and then send this off on the camel route. This approach is just but one way of doing this. Christian Schneider has another excellent solution which I will probably use much more now that I have gotten much more into Camel then ever before. However both will achieve the same thing and depending on who you ask one might be more clear than the other.
For example you can do:
public void foo(Exchange exchange){
exchange.getIn().setHeader("MsgType", "Student");
}
You can then filter on the header in either the Java DSL or even spring DSL.
In Java DSL you would do something like this (pseudo code)
from("foo:incommingroute")
.choice()
.when(header("MsgType").equals("Student"))
.to("webservice:Student")
.when(header("MsgType").equals("Employee"))
.to("webservice:Employee")
.otherwise()
.to("jms:Deadletter")
.end();
In Spring DSL you would do something like this (pseudo code)
<route>
<from uri="foo:incommingroute"/>
<choice>
<when>
<simple>${header.MsgType} equals 'Student'</simple>
<to uri="webservice:Student"/>
</when>
<when>
<simple>${header.MsgType} equals 'Employee'</simple>
<to uri="webservice:Employee"/>
</when>
<otherwise>
<to uri="jms:badOrders"/>
<stop/>
</otherwise>
</choice>
<to uri="jms:Deadletter"/>
</route>
You can also look at the enricher pattern at this link http://camel.apache.org/content-enricher.html. Basically what I am suggesting is following the enricher pattern. If you could tell me how you are sending messages to Camel then I could probably help more.
Hope this give you some ideas and if there is syntax mistakes etc in the code sorry I am at a bus stop and did not have time to check it.
I prefer to write this type of logic directly in the route definition rather than in a Processor. Here is the Camel DSL approach that uses a Predicate to determine the body class type. It assumes that you have already deserialized the Exchange body into a Student or Employee object.
choice()
.when(body().isInstanceOf(Student::class))
.to(...)
.when(body().isInstanceOf(Employee::class))
.to(...)
.end()
If you're going to perform various transformations on the body throughout the route, resulting in a variety of Student or Employee object types at various stages (e.g. a Student then a StudentEntity, etc) then saving the type in a header or property as some String constant at the beginning of the route might be the cleaner approach.
// Note that this labelling could be bundled into a processor
choice()
.when(body().isInstanceOf(Student::class))
.setProperty("TYPE", "STUDENT")
.when(body().isInstanceOf(Employee::class))
.setProperty("TYPE", "EMPLOYEE")
.end()
// later after some body transformations
.choice()
.when(exchangeProperty("TYPE").isEqualTo("STUDENT"))
// process student
Lastly, you might be able to do everything in a processor but I think this sort of branch logic combined with service invocation is a Camel anti-pattern.
class MyProcessor implements Processor {
#Override
public void process(Exchange exchange) {
Object body = exchange.getIn().getBody()
if (body instanceOf Student) {
// invoke StudentService
} else if (body instanceOf Employee) {
// invoke EmployeeService
}
}
}
// Route definition
from(...)
.process(myProcessor)