I'm trying to set a property isEven on the Exchange.property and then use choice when to evaluate it in the route.
The property is being set but I am always getting the otherwise result (NACK) no matter what isEven is set to.
Here is where I set it:
// Below is used for development
// If the property.isEven == true then an ACK will be returned from the Mock HRM
// If false then NACK
int lastDigit = Integer.parseInt(exchange.getExchangeId().substring(exchange.getExchangeId().length() - 1));
// check if lastDigit is odd or even
if ((lastDigit & 1) == 0)
{
exchange.setProperty("isEven", Boolean.TRUE);
System.out.println("\n\n\n********** Exchange Id lastDigit " + lastDigit + " isEven: " + exchange.getProperty("isEven") + " ***********");
}
else
{
exchange.setProperty("isEven", Boolean.FALSE);
System.out.println("\n\n\n********** Exchange Id lastDigit " + lastDigit + " isEven: " + exchange.getProperty("isEven") + " ***********");
}
The println's show that I am setting isEven the way I expect.
Here is the route:
<!-- Used to ramdomly send Ack or Nack -->
<log message="isEven property is :: ${property[isEven]}" />
<camel:choice>
<camel:when>
<simple>"${property[isEven]}"</simple>
<transform>
<constant>ACK</constant>
</transform>
</camel:when>
<camel:otherwise>
<transform>
<constant>NACK</constant>
</transform>
</camel:otherwise>
</camel:choice>
The log message never evaluates the expression ${property[isEven]}
Here is the output
log[isEven property is :: ${property[isEven]}]
if I change the simple expression to explicitly check for true I alway get ACK no matter what the property is set to.
<simple>"${property[isEven]} == true"</simple>
I have searched the web but couldn't find a lot of examples using simple and Exchange property.
Can anyone see what I'm missing?
Thanks,
Andrew
After Peter showed he could do it easily I tried 2 of his examples that I hadn't tried already. Here is one. It didn't work for me. It generated Hello :: NACK whether isEven is true or false:
<camel:choice>
<camel:when>
<simple>${property[isEven]} == "true"</simple>
<log message="HELLO :: ACK" />
<!-- <transform>
<constant>ACK</constant>
</transform> -->
</camel:when>
<camel:otherwise>
<log message="HELLO :: NACK" />
<!-- <transform>
<constant>NACK</constant>
</transform> -->
</camel:otherwise>
</camel:choice>
Here is something interesting. It looks below like the log is saying its empty On the last like
********** Exchange Id lastDigit 2 isEven: true ***********
14/02/20 14:09:13 INFO interceptor.Tracer: >>> (toHRMRoute) bean://hl7handler?method=handleORM --> log[isEven property is :: ${property[isEven]}] <<< Pattern:InOut, Properties {CamelToEndpoint=bean://hl7handler?method=handleORM, CamelMessageHistory [DefaultMessageHistory[routeId=toHRMRoute, node=to3], DefaultMessage History[routeId=toHRMRoute, node=log1]], CamelCreatedTimestamp=Thu Feb 20 14:09:13 CST 2014}
14/02/20 14:09:13 INFO toHRMRoute: ** isEven property is :: **
I think Peter is correct in that it has to be the way I have my routes set up.
<endpoint id="hrmMockHL7Listener"
uri="netty:tcp://localhost:9200?sync=true" />
<!-- Sending data using postman to a rest server-->
<route id="pushRESTRoute">
<from uri="cxfrs://bean://pushRESTServer" />
<!-- this process is where we set isEven on the Exchange-->
<process ref="transformer"/>
<!-- Send it to a tcp listener at port 9200-->
<to ref="hrmMockHL7Listener" />
</route>
<!-- Changed routes does the Exchange keep properties? -->
<route id="toMRoute">
<from uri="hrmMockHL7Listener" />
<to uri="bean:hl7handler?method=handleORM" />
<!-- Used to ramdomly send Ack or Nack -->
<log message="isEven property is :: ${property[isEven]}">
// see the beginning of the question for choice code.
Looking at the output it seems the isEven property is being dropped between routes:
14/02/21 09:37:26 INFO interceptor.Tracer: >>> (pushRESTRoute) ref:transformer --> tcp://localhost:9200 <<< Pattern:InOut, Properties {CamelMessageHistory=[DefaultMessageHistory[routeId=pushRESTRoute, node=process1], DefaultMessageHistory[routeId=pushRESTRoute, node=to1]], CamelCreatedTimestamp=Fri Feb 21 09:37:26 CST 2014, isEven=true}
See isEven at the end? The next tracer that comes doesn't have it
/02/21 09:37:26 INFO interceptor.Tracer: >>> (toMRoute) from(tcp://localhost:9200) --> bean://hl7handler?method=handleORM <<< Pattern:InOut, Properties:{CamellMessageHistory=[DefaultMessageHistory[routeId=toMRoute, node=to3]], CamelCreatedTimestamp=Fri Feb 21 09:37:26 CST 2014}
From the Exchange javadoc
An Exchange is the message container holding the information during the entire routing of a Message received by a Consumer.
Does entire include across differents routes?
For testing I changed the route a little bit:
<route id="startRoute">
<from uri="direct:start" />
<multicast stopOnException="true">
<to uri="direct:trigger" />
<to uri="direct:trigger" />
<to uri="direct:trigger" />
</multicast>
</route>
<route>
<from uri="direct:trigger" />
<process ref="myProcessor" />
<log message="isEven property is :: ${property[isEven]}" />
<camel:choice>
<camel:when>
<simple>"${property.isEven}"</simple>
<log message="HELLO :: ACK" />
</camel:when>
<camel:otherwise>
<log message="HELLO :: NACK" />
</camel:otherwise>
</camel:choice>
</route>
<!-- scope singleton is default -->
<bean id="myProcessor" class="ch.keller.test.testcamelspring.util.Trigger" scope="singleton" />
The processor is defined as follows:
public class Trigger implements Processor {
#Override
public void process(final Exchange exchange) throws Exception {
// your code comes here
}
}
For me, following expressions worked as expected:
<simple>"${property[isEven]}"</simple>
or
<simple>${property[isEven]}</simple>
or
<simple>${property[isEven]} == "true"</simple>
or
<simple>"${property.isEven}"</simple>
or
<simple>${property.isEven} == "true"</simple>
or
<simple>${property.isEven}</simple>
I prefer the last version.
EDIT:
In order to debug if the property is correctly set, enable showProperties in your Spring configuration file:
<bean id="traceFormatter" class="org.apache.camel.processor.interceptor.DefaultTraceFormatter">
<property name="showBreadCrumb" value="false" />
<property name="showProperties" value="true" />
</bean>
Then you should see following output in your log (shorten for better readability):
[main] Tracer INFO >>> (route1) log[isEven property is :: ${property[isEven]}] --> choice <<< Pattern:InOnly, Properties:{CamelToEndpoint=direct://trigger, ..., isEven=true, ...
The important part is isEven=true.
EDIT:
The property is kept when forwarding to another route which can be proven as follows:
<route>
<from uri="direct:trigger" />
<process ref="myProcessor" />
<log message="isEven property is :: ${property[isEven]}" />
<to uri="direct:acktarget" />
</route>
<route>
<from uri="direct:acktarget" />
<log message="acktarget: isEven property is :: ${property[isEven]}" />
</route>
Output:
exchange.getExchangeId() = ID-pis-iMac-local-54434-1393006139076-0-3
********** Exchange Id lastDigit 3 isEven: false ***********
[ main] route1 INFO isEven property is :: false
[ main] route2 INFO acktarget: isEven property is :: false
exchange.getExchangeId() = ID-pis-iMac-local-54434-1393006139076-0-4
********** Exchange Id lastDigit 4 isEven: true ***********
[ main] route1 INFO isEven property is :: true
[ main] route2 INFO acktarget: isEven property is :: true
exchange.getExchangeId() = ID-pis-iMac-local-54434-1393006139076-0-5
********** Exchange Id lastDigit 5 isEven: false ***********
[ main] route1 INFO isEven property is :: false
[ main] route2 INFO acktarget: isEven property is :: false
Even if I invoke a bean before forwarding the message to the other route, the property is kept. So, I guess your problems is in <to uri="bean:hl7handler?method=handleORM" />. Try to log the property before invoking this bean and see if the property is still set. If not, have a look at the bean.
For newer version of apache camel use exchangeProperty instead of property
${exchangeProperty.isEven}
The simple language property function was deprecated in Camel 2.x and has been removed. Use exchangeProperty as function name.
You do not need to use quotes. Try just
<simple>${property.isEven} == true</simple>
Related
I am trying to connect to RabbitMQ queues present in the server using apache-camel configuration.
It works fine when I create the queues with durable field false and auto-delete field true. But doesn't work when either of them is otherwise.
applicationContext.xml file looks like this -
<bean id="customConnectionFactory" class="com.rabbitmq.client.ConnectionFactory">
<property name="host" value="localhost" />
<property name="port" value="5672" />
<property name="username" value="guest" />
<property name="password" value="guest" />
<property name="virtualHost" value="Test" />
</bean>
<bean id="testBean" class="test.TestBean" />
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from
uri="rabbitmq://localhost:5672/ex1?connectionFactory=#customConnectionFactory&queue=Q1&autoDelete=true&durable=true" />
<to uri="bean:testBean?method=hello" /> <!-- This method consumes and prints the message -->
</route>
</camelContext>
Here I need to specify the properties autoDelete and durable for the queue Q1 not the exchange ex1. (I have already specified for the exchange in the URI)
As the error is -
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'auto_delete' for queue 'Q1' in vhost 'Test': received 'true' but current is 'false', class-id=50, method-id=10)
Here reply-code=406 indicates that the parameters of queues/exchanges are not matching with the actual configuration. Its because of the queues properties here.
As I don't have the access to the remote queues, I cannot change the properties of queues. (Example I stated here is localhost)
Note: I have a requirement of doing this using spring beans only.
I'm trying to create a webservice which, when called look into a local directory picks up files from there and upload to the ftp server.
I'm able to create a simple route which picks file from local directory and uploads to ftp server below is the code :
<route>
<from uri="file://D:\\FTPTest?noop=true&delay=2000" />
<to uri="ftp://user#host.in:21/public_html/EnterpriseProject?password=password123#"/>
<to uri="bean:myBean?method=test" />
</route>
But, I want to this file transfer to be called when a particular route is called via restlet webservice is called, I tried with the following code, but it didn't work :
<route>
<from uri="direct:fileTransferRoute" />
<to uri="file://D:\\FTPTest?noop=true&delay=2000" />
<to uri="ftp://user#host.in:21/public_html/EnterpriseProject?password=password123#"/>
</route>
The above route is called by restlet from following route :
<route>
<from
uri="restlet:http://0.0.0.0:9080/csitec/{serviceName}?restletMethod=post" />
<process ref="serviceRouteProcessor" />
<toD uri="direct:${in.header.nextRoute}" />
</route>
Here's the code of my serviceRouteProcessor :
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
String serviceName = exchange.getIn().getHeader(Constants.SERVICE_NAME).toString();
String nextRoute = serviceName+Constants.NEXT_ROUTE_APPENDER;
exchange.getOut().setHeader(Constants.NEXT_ROUTE, nextRoute);
exchange.getOut().setBody(body);
}
Please help me and suggest the changes needs to be done to make it work like this.
You should try the pollEnrich feature of content-enricher
In the example section you can find a example regarding files.
Your route should look something like this(I work only with camel java dsl, so this a bit xml pseudo code):
<route>
<from uri="direct:fileTransferRoute" />
<pollEnrich uri="file://D:\\FTPTest?fileName=data.txt....." />
<to uri="ftp://user#host.in:21/public_html/EnterpriseProject?password=password123#"/>
</route>
Edited :
you must understand one thing first , to is producer not consumer <to uri="file://D:\\FTPTest?noop=true&delay=2000" />
What you can do is ,
#Autowired
private CamelContext context;// if you have more than one camel context use #Qualifier and wire by bean id
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
String serviceName = exchange.getIn().getHeader(Constants.SERVICE_NAME).toString();
context.startRoute(serviceName+Constants.NEXT_ROUTE_APPENDER);// here in nextroute you must give the routeid
}
your route must look like
<route id = "<value of serviceName+Constants.NEXT_ROUTE_APPENDER>" autoStartup = "false">
<from uri="file://D:\\FTPTest..." />
<onCompletion onFailureOnly="true">
<choice>
<when>
<simple>${property.CamelBatchComplete}</simple>
<process ref="asyncSelfShutdownProcessor"/>
</when>
</choice>
</onCompletion>
<to uri="ftp://user#host.in:21..."/>
</route>
And add asyncSelfShutdownProcessor to spring context
#Component
public class AsyncSelfShutdownProcessor implements AsyncProcessor {
#Autowired
private CamelContext context
public boolean process(Exchange exchange, AsyncCallback callback){
new Thread(() -> context.stopRoute(exchange.getFromRouteId())).start();
}
}
##############################################################################
Old :
OK I understand your need as - you have a route that moves file from file system to ftp server, all you need is this route to get executed only when you trigger from a rest service. I would do it like this ,
*I will make the route autoStartup = "false" and assign as id = "fs-to-ftp" to the route
<route id = "fs-to-ftp" autoStartup = "false">
<from uri="file://D:\\FTPTest..." />
<onCompletion onFailureOnly="true">
<process ref="asyncSelfShutdownProcessor"/>
</onCompletion>
<to uri="ftp://user#host.in:21..."/>
</route>
**Add a self shutdown async process in onComplete to the route "fs-to-ftp". Async Processor
asyncSelfShutdownProcessor= AsyncProcessorConverterHelper.convert(exchange -> {
new Thread(() -> context.stopRoute("fs-to-ftp")).start();
});
***Add the camel context dependency to rest service and start the route by id in the rest service context.startRoute("fs-to-ftp")
I am having an hard time tracking down an issue with my Camel routes. From what Ive been reading, it seems it may be something to do with my routing key header information getting screwed up, but Im not sure how to resolve this. Also, this is a Java OSGi project if thats important, but all the Camel stuffs are currently implemented in XML. Any help is appreciated.
Here's what Im trying to do:
<!-- The first route creates an object with some info in it and drop it on a rabbitmq
exchange called message.added -->
<route id="directIn">
<from uri="direct:in" />
<bean ref="connector" method="handleIncoming" />
<marshal id="marshal-one" ref="firstObject" />
<to uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true&routingKey=message.added" />
</route>
<!-- This route listens to message.added, processes the data, creates a new object, and
drops it on a different rabbitmq exchange called message.rest -->
<route id="addedOne">
<from uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true&routingKey=message.added" />
<unmarshal id="unmarshal-one" ref="firstObject" />
<bean ref="connector" method="processAndConvert" />
<marshal id="marshal-out" ref="secondObject" />
<to uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true&routingKey=message.rest" />
</route>
This code seems to have been working fine. The problem arises when we add a new bundle, which is also listening to the message.added rabbitmq message (which I believe you are allowed to do?)
<!-- This route is in a different bundle, also listening to message.added, processing
the data and creating the same object (from a common bundle) and dropping it
on the same rabbitmq exchange as before -->
<route id="addedTwo">
<from uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true&routingKey=message.added" />
<unmarshal id="unmarshal-two" ref="firstObject" />
<bean ref="someService" method="processUpdate" />
<marshal id="marshal-out2" ref="secondObject" />
<to uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true&routingKey=message.rest" />
</route>
This route fails with an error message that it is trying to unmarshal a secondObject into a firstObject in the addedTwo route.
ERROR | RabbitMQConsumer | DefaultErrorHandler ....
caught: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "secondObject" (class com.pointer.dangling.FirstObject["secondObject"])
Message History:
RouteId ProcessorId Processor
[addedTwo] [addedTwo] [ ]
[addedTwo] [unmarshal-two] [unmarshal[ref:firstObject] ]
Exchange:
Headers: {
CamelRedelivered=false,
rabbitmq.DELIVERY_TAG=1,
rabbitmq.EXCHANGE_NAME=me.ex,
rabbitmq.ROUTING_KEY=message.added
}
Body: {
"secondObject": {
// a bunch of fields for secondObject
}
}
It appears that a 'secondObject' is, at some point, making its way onto the 'message.added' exchange and being picked up by the 'addedTwo' route which tries to marshal it into a 'firstObject'. But Im not explicitly telling it to do that anywhere in the code - any ideas?
In the RabbitMQ Camel component, the routingKey endpoint option only works for Consumers (<from />).
Producers must set their routing key explicitly as a message header before the <to />. Your addedOne route should look like this:
<route id="addedOne">
<from uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true&routingKey=message.added" />
<unmarshal id="unmarshal-one" ref="firstObject" />
<bean ref="connector" method="processAndConvert" />
<marshal id="marshal-out" ref="secondObject" />
<setHeader headerName="rabbitmq.ROUTING_KEY">
<constant>message.rest</constant>
</setHeader>
<to uri="rabbitmq://localhost:5672/me.ex?exchangeType=topic&durable=false&autoDelete=true" />
</route>
See https://camel.apache.org/rabbitmq.html for more info.
I have setup my current project to run with Selenium and PhantomJS. (Similar setup to http://www.nuanced.it/2015/05/using-phantomjs-with-phpunit.html)
However I keep on getting following error
{"errorMessage":"Can only set Cookies for the current domain","request":{"headers":{"Accept":"application/json;charset=UTF-8","Content-Length":"135","Content-type":"application/json;charset=UTF-8","Host":"127.0.0.1:8080"},"httpVersion":"1.1","method":"POST","post":"{\"cookie\":{\"name\":\"PHPUNIT_SELENIUM_TEST_ID\",\"value\":\"PepperLeaf\\\\WebBundle\\\\Tests\\\\MyProject\\\\TestFirstTest__testTitle\",\"secure\":false}}","url":"/cookie","urlParsed":{"anchor":"","query":"","file":"cookie","directory":"/","path":"/cookie","relative":"/cookie","port":"","host":"","password":"","user":"","userInfo":"","authority":"","protocol":"","source":"/cookie","queryKey":{},"chunks":["cookie"]},"urlOriginal":"/session/0d022670-4f79-11e5-abbe-01f06bc40b42/cookie"}}
Output from PhantomJS
[INFO - 2015-08-31T00:41:02.249Z] HUB Register - register - Registered with grid hub: http://127.0.0.1:4444/ (ok)
[INFO - 2015-08-31T00:41:43.258Z] Session [0d022670-4f79-11e5-abbe-01f06bc40b42] - page.settings - {"XSSAuditingEnabled":false,"javascriptCanCloseWindows":true,"javascriptCanOpenWindows":true,"javascriptEnabled":true,"loadImages":true,"localToRemoteUrlAccessEnabled":false,"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.0.0 Safari/538.1","webSecurityEnabled":true}
[INFO - 2015-08-31T00:41:43.258Z] Session [0d022670-4f79-11e5-abbe-01f06bc40b42] - page.customHeaders: - {}
[INFO - 2015-08-31T00:41:43.258Z] Session [0d022670-4f79-11e5-abbe-01f06bc40b42] - Session.negotiatedCapabilities - {"browserName":"phantomjs","version":"2.0.0","driverName":"ghostdriver","driverVersion":"1.2.0","platform":"mac-10.9 (Mavericks)-64bit","javascriptEnabled":true,"takesScreenshot":true,"handlesAlerts":false,"databaseEnabled":false,"locationContextEnabled":false,"applicationCacheEnabled":false,"browserConnectionEnabled":false,"cssSelectorsEnabled":true,"webStorageEnabled":false,"rotatable":false,"acceptSslCerts":false,"nativeEvents":true,"proxy":{"proxyType":"direct"}}
[INFO - 2015-08-31T00:41:43.258Z] SessionManagerReqHand - _postNewSessionCommand - New Session Created: 0d022670-4f79-11e5-abbe-01f06bc40b42
Code that I run on PHPUnit
class TestFirstTest extends \PHPUnit_Extensions_Selenium2TestCase {
/**
* {#inheritDoc}
*/
protected function setUp()
{
$this->setBrowser('phantomjs');
$this->setHost("127.0.0.1");
$this->setPort(8080);
$this->setBrowserUrl('http://www.mywebsite.dev/');
}
public function testTitle()
{
$this->url('/');
$this->assertTitle('Example WWW Page');
}
}
Disclaimer: I'm quite new to front end testing, this is my first go at this. I'm confused why this would be happening as I'm not setting up nor creating any cookies/sessions.
I'm not sure exactly what has caused this problem. However, this is my symfony/phpunit.xml (this is what has caused problems)
<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit
backupGlobals = "false"
backupStaticAttributes = "false"
colors = "false"
convertErrorsToExceptions = "true"
convertNoticesToExceptions = "true"
convertWarningsToExceptions = "true"
processIsolation = "false"
stopOnFailure = "false"
syntaxCheck = "false"
bootstrap = "../app/bootstrap.php.cache" >
<testsuites>
<testsuite name="PepperLeaf">
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>
<php>
<server name="KERNEL_DIR" value="app/" />
<ini name="memory_limit" value="2048M" />
<ini name="xdebug.max_nesting_level" value="500" />
</php>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
<exclude>
<directory>../src/*/*Bundle/Resources</directory>
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Resources</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/FrontTests</directory>
<directory>../src/*/Bundle/*Bundle/DataFixtures</directory>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="coverage" />
<log type="coverage-xml" target="coverage/xml" />
<log type="coverage-clover" target="logs/clover.xml" />
<log type="coverage-crap4j" target="logs/crap4j.xml" />
<log type="junit" target="logs/junit.xml" logIncompleteSkipped="false" />
</logging>
</phpunit>
This is updated (working phpunit.xml script that WORKS). I'm not sure what exactly caused the problem above.
<?xml version="1.0" encoding="UTF-8"?>
<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit>
<testsuites>
<testsuite name="PepperLeaf_Frontend">
<directory>../src/*/*Bundle/FrontTests</directory>
</testsuite>
</testsuites>
<php>
<server name="KERNEL_DIR" value="app/" />
<ini name="memory_limit" value="2048M" />
<ini name="xdebug.max_nesting_level" value="500" />
</php>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
<exclude>
<directory>../src/*/*Bundle/Resources</directory>
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Resources</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/DataFixtures</directory>
<directory>../src/*/*Bundle/FrontTests</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
I am using Spring's message-driven-channel-adapter.
My component is consuming message from Tibco Topic and Publishing to RabbitMQ topic
So The message flow is as follows:
Tibco-> (subscribed by )Component (Published to)-> RabbitMQ
The service activator is shown below: as we see there is a input-channel and an output-channel. The bean storeAndForwardActivator will have the business logic (within the method createIssueOfInterestOratorRecord)
<int:service-activator input-channel="inboundOratorIssueOfInterestJmsInputChannel"
ref="storeAndForwardActivator" method="createIssueOfInterestOratorRecord"
output-channel="outboundIssueOfInterestRabbitmqOratorJmsOutputChannel" />
I also have a message=driven-channel-adapter. This adapter will be invoked before the service adapter is invoked.
<int-jms:message-driven-channel-adapter
id="oratorIssueOfInterestInboundChannel" channel="inboundOratorIssueOfInterestJmsInputChannel"
container="oratorIssueOfInterestmessageListenerContainer" />
i.e. specifically the container (shown below) will hold the Topic name to be used - this is the DefaultMessageListenerContainer
<bean id="oratorIssueOfInterestmessageListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="oratorIssueOfInterestTibcoConnectionFactory" />
<property name="destination" ref="oratorTibcojmsDestination" />
<property name="sessionTransacted" value="true" />
<property name="maxConcurrentConsumers" value="1" />
<property name="concurrentConsumers" value="1" />
<property name="receiveTimeout" value="5000" />
<property name="recoveryInterval" value="60000" />
<property name="autoStartup" value="true" />
<property name="exposeListenerSession" value="false" />
<property name="subscriptionDurable" value="true" />
<property name="durableSubscriptionName" value="${topic.orator.durable-subscription-name}" />
<property name="messageSelector" value="${topic.orator.selector}" />
</bean>
This set up works perfectly fine. However in some cases my consumer/component receives a 'rogue' message. i.e. an empty payload or a message type of HashMap (instead of plain TextMessage) - when we get this - what I observe is - an exception is caught at the DefaultMessageListener level (i.e. I don't go as far as my business bean i.e. storeAndForwardActivator), because of this my component is not sending ACK back - and since this is a durable Topic - there is a build of messages at the Topic - which is undesirable.
Is there a way for me to ACK the message straight away irrespective of weather an exception is caught at the DefaultMessageListener level?
Or should I introduce an error handler at the DefaultMessageListener?
What's the best way to handle this, any suggestions?
regards
D
Update:
I tried adding a errorHandler to the org.springframework.jms.listener.DefaultMessageListenerContainer
as shown below
<bean id="oratorIssueOfInterestmessageListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="oratorIssueOfInterestTibcoConnectionFactory" />
<property name="destination" ref="oratorTibcojmsDestination" />
<property name="sessionTransacted" value="true" />
<property name="maxConcurrentConsumers" value="1" />
<property name="concurrentConsumers" value="1" />
<property name="receiveTimeout" value="5000" />
<property name="recoveryInterval" value="60000" />
<property name="autoStartup" value="true" />
<property name="exposeListenerSession" value="false" />
<property name="subscriptionDurable" value="true" />
<property name="durableSubscriptionName" value="${topic.orator.durable-subscription-name}" />
<property name="messageSelector" value="${topic.orator.selector}" />
<property name="errorHandler" ref="myErrorHandler"/>
</bean>
myErrorHandler is a bean as shpwn below
<bean id="myErrorHandler"
class="com.igate.firds.icmf.activators.concentrator.MyErrorHandler" />
MyErroHandler implements ErrorHandler
#Service
public class MyErrorHandler implements ErrorHandler{
private static Log log = LogFactory.getLog(MyErrorHandler.class);
#Override
public void handleError(Throwable t) {
if (t instanceof MessageHandlingException) {
MessageHandlingException exception = (MessageHandlingException) t;
if (exception != null) {
org.springframework.messaging.Message<?> message = exception.getFailedMessage();
Object payloadObject = message.getPayload();
if (null != payloadObject) {
log.info("Payload is not null, type is: " + payloadObject.getClass());
}
}
} else {
log.info("Exception is not of type: MessageHandlingException ");
}
}
}
What I notice is that the exception is caught (when the subscriber consumes a rogue message). I keep on seeing this log in a loop
Exception is not of type: MessageHandlingException
Exception is not of type: MessageHandlingException
Exception is not of type: MessageHandlingException
i.e. since the transaction is not committed - the same message from durable topic is consumed again and again. My aim is to send an ACK back to the broker after consuming the message (irrespective of weather an exception is caught or not).
I will try the error-channel tomorrow.
regards
D
Add an error-channel to the message-driven adapter; the ErrorMessage will contain a MessagingException payload that has two fields; the cause (exception) and failedMessage.
If you use the default error-channel="errorChannel", the exception is logged.
If you want to do more than that you can configure your own error channel and add some flow to it.
EDIT:
Further to your comments below...
payload must not be null is not a stack trace; it's a message.
That said, payload must not be null looks like a Spring Integration message; it is probably thrown in the message listener adapter during message conversion, which is before we get to a point where the failure can go to the error-channel; such an exception will be thrown back to the container.
Turn on DEBUG logging and look for this log entry:
logger.debug("converted JMS Message [" + jmsMessage + "] to integration Message payload [" + result + "]");
Also, provide a FULL stack trace.
EDIT#2
So, I reproduced your issue by forcing the converted payload to null in a custom MessageConverter.
The DMLC error handler is called by the container after the transaction is rolled back so there's no way to stop the rollback.
We can add an option to the adapter to handle such errors differently but that will take some work.
In the meantime, a work-around would be to write a custom MessageConverter; something like the one in this Gist.
Then, your service will have to deal with handling the "Bad Message Received" payload.
You then provide the custom converter like this...
<jms:message-driven-channel-adapter id="jmsIn"
destination="requestQueue" acknowledge="transacted"
message-converter="converter"
channel="jmsInChannel" />
<beans:bean id="converter" class="foo.MyMessageConverter" />