How to manually ack/nack a PubSub message in Camel Route - apache-camel

I am setting up a Camel Route with ackMode=NONE meaning acknowlegements are not done automatically. How do I explicitly acknowledge the message in the route?
In my Camel Route definition I've set ackMode to NONE. According to the documentation, I should be able to manually acknowledge the message downstream:
https://github.com/apache/camel/blob/master/components/camel-google-pubsub/src/main/docs/google-pubsub-component.adoc
"AUTO = exchange gets ack’ed/nack’ed on completion. NONE = downstream process has to ack/nack explicitly"
However I cannot figure out how to send the ack.
from("google-pubsub:<project>:<subscription>?concurrentConsumers=1&maxMessagesPerPoll=1&ackMode=NONE")
.bean("processingBean");
My PubSub subscription has an acknowledgement deadline of 10 seconds and so my message keeps getting re-sent every 10 seconds due to ackMode=NONE. This is as expected. However I cannot find a way to manually acknowledge the message once processing is complete and stop the re-deliveries.

I was able to dig through the Camel components and figure out how it is done. First I created a GooglePubSubConnectionFactory bean:
#Bean
public GooglePubsubConnectionFactory googlePubsubConnectionFactory() {
GooglePubsubConnectionFactory connectionFactory = new GooglePubsubConnectionFactory();
connectionFactory.setCredentialsFileLocation(pubsubKey);
return connectionFactory;
}
Then I was able to reference the ack id of the message from the header:
#Header(GooglePubsubConstants.ACK_ID) String ackId
Then I used the following code to acknowledge the message:
List<String > ackIdList = new ArrayList<>();
ackIdList.add(ackId);
AcknowledgeRequest ackRequest = new AcknowledgeRequest().setAckIds(ackIdList);
Pubsub pubsub = googlePubsubConnectionFactory.getDefaultClient();
pubsub.projects().subscriptions().acknowledge("projects/<my project>/subscriptions/<my subscription>", ackRequest).execute();

I think it is best if you look how the Camel component does it with ackMode=AUTO. Have a look at this class (method acknowledge)
But why do you want to do this extra work? Camel is your fried to simplify integration by abstracting away low level code.
So when you use ackMode=AUTO Camel automatically commits your successfully processed messages (when the message has successfully passed the whole route) and rolls back your not processable messages.

Related

Camel netty consumer always sends immediate response even if route not finished

I am using apache camel 3.8 and trying to make a simple tcp server that sends the request message back to the client (the client I am using is PacketSender).
The problem is, if the process() method takes too long an empty response is sent back by netty in the background after 15ms, even if the process() is still in the Thread.sleep hold.
If I do not let the method (thread) sleep, the response is sent with the received content immediatly.
How can I manage it, that netty is waiting until I finished my process and send the response I set in the exchange message body?
Here is the route I am using:
fromF("netty:tcp://%s:%d?sync=true&synchronous=true&disconnectOnNoReply=false&connectTimeout=100000", host, receivePort)
.bean(HL7Request.class, "process", BeanScope.Request);
The bean process method looks like this, for simulation purpose of my long taking process I used a Thread.sleep:
public void process(Exchange exchange) throws Exception {
try {
CamelContext context = exchange.getContext();
exchange.setException(null);
Thread.sleep(5000); // <-- Here the method stopps for 5 seconds but the response is sent by netty anyway
String content = exchange.getMessage().getBody(String.class);
System.out.println(content);
}
catch (Exception e) {
exchange.setException(e);
}
}
PacketSender receives an empty response
Thank you.
Regards,
Florian
I think it was an issue with PacketSender Software. It perfectly works using command line telnet.
More details to the bug

Resume Activiti task from Camel ActiveMQ route

I'm trying to send a message from an Activiti Camel task to an ActiveMQ queue, which should resume the activity when it is received by Camel. As I understand it, when the message is received from the queue lacks the properties that would enable it to be identified by Camel in order to be routed to the correct activity task.
As such a Business key is Null Exception is raised and the route fails.
from("activiti:SampleProcess:myCamelTask")
.to("activemq:queue:myQueue");
As expected, if I hardcode either the PROCESS_ID_PROPERTY or the PROCESS_KEY_PROPERTY in the receiving route, the message is routed correctly (when the ID matches).
from("activemq:queue:myQueue")
.setBody(constant("test body"))
.setProperty(PROCESS_ID_PROPERTY, constant("50"))
// .setProperty(PROCESS_KEY_PROPERTY, constant("CUSTOM-KEY"))
.to("activiti:SampleProcess:receiveAsyncPing");
How can I get either property in the receiving route so I can set them accordingly?
Or is there a more recommended way to approach this?
A good question.
The way I handled this is to inject the PROCESS_KEY within the route using the setProperty() command:
See below where I set the process key (business key) to "bar":
from(startProcessEndpoint)
.log(LoggingLevel.INFO, logMsg3)
.setProperty("PROCESS_KEY_PROPERTY",foo)
.setBody(constant("bar"))
.to("activiti:testCamelTask:receive")
Now, if you dont want to use a constant, then you have access to the exchange object within the route and can use an Expression as shown below:
Expression foo = new Expression() {
#Override
public <T> T evaluate(Exchange exchange, Class<T> aClass) {
return (T) "foo";
}
};
Hope this helps,
Greg

no messages consumed from queue

I am trying to consume the message from Amazon SQS using camel.
<from uri="aws-sqs://{{aws.sqs.queue}}?amazonSQSClient=#sqsClient&delay=10000"/>
<to uri="bean:sqsQueueListener?method=consumeMessage(${body})"/>
The handler looks like this (I originally did not specify it in xml because annotation should have handle it but it did not work so I defined it directly)
class SqsQueueListener {
#Handler
public void consumeMessage(List<Message> messages) {
LOGGER.info("Got messages: [{}].", messages);
}
}
Every 10 seconds I get the message regarding instantiation of AmazonSQS client in my application as it is trying to read from queue. But nothing happens then. I don't get the message about consumed messages.
What is the problem?
Have you added your handler to the registry attached to the camel context with the name "sqsClient"?

Camel ApacheMQ -> AHC behaviour (blocking?)

I just started using Apache Camel and I'm curious about the seemingly counter-intuitive default behaviour of asynchronous http client (AHC). While consuming messages from ActiveMQ, I can't get it to act in a non-blocking fashion.
My route looks like this:
#Component
public class Broadcaster extends RouteBuilder {
#Override
public void configure() throws Exception {
errorHandler(deadLetterChannel("activemq:failed.messages"));
from("activemq:outbound.messages")
.setExchangePattern(ExchangePattern.InOnly)
.recipientList(simple("ahc:${in.header[PublishDestination]}"))
.end();
}
}
I enqueued several messages, half of which I sent to a delayed web server, and the other half to a normal one. I expected to see all the normal messages consumed immediately by the fast server, and the slow messages gradually over time. However, this was the behaviour observed on the fast web server:
00:24:02.585, <hello>World</hello>
00:24:03.622, <hello>World</hello>
00:24:04.640, <hello>World</hello>
00:24:05.658, <hello>World</hello>
As you can see there is exactly one second between each logged request that corresponds to the artificial 1 second delay on the slow server. Based on the route timings, it looks like the JMS consumer is waiting for AHC to complete before it consumes the next message off the queue:
Processor Elapsed (ms)
[activemq://outbound.messages ] [ 1020]
[setExchangePattern[InOnly] ] [ 0]
[ahc:${in.header[PublishDestination]}} ] [ 1018]
Am I supposed to explicitly use async producers and write callback handlers in these cases, or is there something else I'm missing? Thank you!
Well, case of a RTFM I guess, although I ActiveMQ page leaves a lot to be desired in terms of properties available for endpoint configuration. There should probably be a note to say most (all?) JMS config options are also available for ActiveMQ component. In any case, the solution is to define the consumer as follows:
from("activemq:outbound.messages?asyncConsumer=true")

How to broadcast a message within the same JVM process with Camel?

I am consuming a directory and I would like to broadcast a message to listeners within the same JVM process. I don't know who the interested parties are because they register themselves when they come up: the set of services within my JVM process depends on configuration.
Multicast does not seem to be what I want because I don't know at route build time where to send messages.
Besides using a queuing solution (ActiveMQ, RabbitMQ), are there other solutions?
Beside the queuing solutions (JMS/ActiveMQ and RabbitMQ), you could use the VM component for intra JVM communication. VM is an extension of the SEDA component. In contrast to SEDA that can only be used for communication between different routes in a single Camel context, VM can be used for communication between routes running in different contexts.
Sending a message:
final ProducerTemplate template = context.createProducerTemplate();
template.sendBody("vm:start", "World!");
With multipleConsumers=true it is possible to simulate Publish-Subscribe messaging, i.e. it is possible to configure more than one consumer:
from("vm:start?multipleConsumers=true")
.log("********** Hello: 1 ************");
from("vm:start?multipleConsumers=true")
.log("********** Hello: 2 ************");
This prints:
route1 INFO ********** Hello: 1 ************
route2 INFO ********** Hello: 2 ************
However, in contrast to JMS/ActiveMQ and RabbitMQ, the messages can not leave the JVM. And the messages are not persisted. That means that the messages are lost, a) if no consumer has been started when the message is sent, b) if the JVM crashes before the messages are consumed.
use the recipient list pattern as it resolves the destination endpoints at runtime...
for example, you could implement a method to dynamically determine the recipients, etc...
from("direct:test").recipientList().method(MessageRouter.class, "routeTo");
public class MessageRouter {
public String[] routeTo() {
return new String[] {
"direct:a", "direct:b"
};
}
}
The Camel SEDA component can give you this. However, it's only valid inside of the current Camel context. If that restriction works for you it's the way to go. It'll handle both a queue style messaging system or pub/sub.
Camel SEDA Component

Resources