JMSReplyTo - How to create a generic camel route for VM/Artemis/IBM MQ - apache-camel

I have the following service.
Spring boot 2.5.13
Camel 3.18.0
JMS
I want to use an embedded ActiveMQ Artemis, standalone ActiveMQ Artemis, and IBM MQ.
I've managed to get all 3 running and connecting, but one thing I cant figure out is the JMSReplyTo option.
Running locally with embedded broker:
This runs fine. I can write a message to the queue and a response is send to the JMSReplyTo:
public void sendRequest(){
ActiveMQQueue activeMQQueue = new ActiveMQQueue("RESPONSE_QUEUE");
jmsTemplate.convertAndSend("REQUEST_QUEUE", "Hello", pp -> {
pp.setJMSReplyTo(activeMQQueue);
return pp;
});
}
Via ActiveMQ Artemis console:
This is where the inconstancy comes as the Object received is an ActiveMQDestination which makes setting the CamelJmsDestination much more involved.
Am I wasting my time here? Should I just grab the queue name and construct the uri manually? Or I am missing some logic as to how this works? Or maybe I'm not using the Artemis console in the correct way?
.setExchangePattern(ExchangePattern.InOut)
.setHeader("CamelJmsDestination", header("JMSReplyTo"))

When using javax.jms.Message#setJMSReplyTo(Destination) you have to pass a javax.jms.Destination which must implement one of the following:
javax.jms.Queue
javax.jms.TemporaryQueue
javax.jms.Topic
javax.jms.TemporaryTopic
In order to reproduce this semantic via text in the web console of ActiveMQ Artemis you need to prefix your destination's name with one of the following respectively:
queue://
temp-queue://
topic://
temp-topic://
So when you set the JMSReplyTo header try using queue://RESPONSE_QUEUE.
When your application then receives this message and invokes getJMSReplyTo() it will receive a javax.jms.Queue implementation (i.e. ActiveMQQueue) and then you can use getQueueName() to get the String name of the queue if necessary.

Related

Use string as Apache Camel endpoint?

Requisite disclaimer about being new to Camel--and, frankly, new to developing generally. I'd like to have a string generated as the output of some function be the source of my camel route which then gets written to some file. It's the first part that seems challenging: I have a string, how do I turn it into a message? I can't write it into a file nor can I use JMS. I feel like it should be easy and obvious, but I'm having a hard time finding a simple guide to help.
Some pseudo-code using the Java DSL:
def DesiredString() {return "MyString";}
// A camel route to be implemented elsewhere; I want something like:
class MyRoute() extends RouteBuilder {
source(DesiredString())
.to("file://C:/out/?fileName=MyFileFromString.txt");
}
I vaguely understand using the bean component, but I'm not sure that solves the problem: I can execute my method that generates the string, but how do I turn that into a message? The "vague" is doing a lot of work there: I could be missing something there.
Thanks!
Not sure if I understand your problem. There is a bit of confusion about what the String should be become: the route source or the message body.
However, I guess that you want to write the String returned by your method into a File through a Camel route.
If this is correct, I have to clarify first the route source. A Camel Route normally starts with
from(component:address)
So if you want to receive requests from remote via HTTP it could be
from("http4:localhost:8080")
This creates an HTTP server that listens on port 8080 for messages.
In your case I don't know if the method that returns the String is in the same application as the Camel route. If it is, you can use the Direct component for "method-like" calls in the same process.
from(direct:input)
.to("file:...");
input is a name you can freely choose. You can then route messages to this route from another Camel route or with a ProducerTemplate
ProducerTemplate template = camelContext.createProducerTemplate();
template.sendBody("direct:input", "This is my string");
The sendBody method takes the endpoint where to send the message and the message body. But there are much more variants of sendBody with different signatures depending on what you want to send it (headers etc).
If you want to dive into Camel get a copy of Camel in Action 2nd edition. It contains everything you need to know about Camel.
Example:Sending String(as a body content)to store in file using camel Java DSL:
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
public void configure() {
from("timer:StringSentToFile?period=2000")
.setBody(simple(DesiredString()))
.to("file:file://C:/out/?fileName=MyFileFromString.txt&noop=true")
.log("completed route");
}
});
ProducerTemplate template = context.createProducerTemplate();
context.start();

Message count from Email-Inbox using Apache camel

I am trying to get the number of Messages(count) from Email-INBOX using Apache camel Mail component.
from("imap://route-test-result#localhost?fetchsize=-1&delay=100")
But unfortunately I am unable to get number of Messages(count) from Inbox.
Does Apache camel provides any solution. Message count parameter is available in Apache Camel Header.
if so please provide me the solution.
The component, out of the box, does not give you that information as it split each mail message and process it individually. You may feed an additional destination with the result of your source like the following:
from("imap:yourConfig").log("${body}").bean(new MailCounter());
...
class MailCounter{
int mailCounter = 0;
public void count(String message){
mailCounter++;
System.out.println("You've received "+mailCounter+" so far...");
}
}
Of course, you must work a little bit to reset the counter synch-ing with the mail consumer polling...

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

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.

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

How to consume from AMQP queue in browsing mode with Apache Camel?

Is there something like "browse" option (see section 2.4.3.3. browse for details) for amqp endpoint?
There is a "browse" component. to("browse:dummy"), but that will only make it possible to browse through messages that has passed this route. Handy in some cases, but not really as the JMS browse option.
What you can do is to use a BrowsableEndpoint to do a JMS browse (should work with AMQP as well, as it's based on a JMS client, haven't tried though).
You can't really receive a message and not delete it though, so you would need something else to trigger your browsing. Such as a timer or a trigger queue.
from("amqp:queue:trigger")
.process(new Processor(){
#Override
public void process(Exchange arg0) throws Exception {
BrowsableEndpoint browse = arg0.getContext().getEndpoint("amqp:queue:archive", BrowsableEndpoint.class);
List<Exchange> exchanges = browse.getExchanges();
System.out.println("Browsing queue: "+ browse.getEndpointUri() + " size: " + exchanges.size());
for (Exchange exchange : exchanges) {
String payload = exchange.getIn().getBody(String.class);
String msgId = exchange.getIn().getHeader("JMSMessageID", String.class);
System.out.println(msgId + "=" +payload);
}
}
});
You could do a pre-route to achive this trigger.
from("amqp:queue:processQueue")
.to("amqp:queue:archive")
.transform().constant("trigger msg")
.to("amqp:queue:trigger");
I figured out it eventually, I just thought that the option should be there on camel component options level, i.e. after connection string in form of e.g. "?mode=browse", but the option is actually to be set on amqp connection level - "my-queue; {mode: browse}". You can set camel component options adding "?option=value".
P.S. Setting the option on amqp connection level works for even-driven (default camel) routing, it does not work however for polling-driven one, see https://issues.apache.org/jira/browse/CAMEL-6784 for details.
Yes. In AMQP 0-10 there is an acquire mode option on message.subscribe which if set tonot-acquired results in a browsing subscription.In AMQP 1.0 the same is achieved by specifying a distribution mode of 'copy' for the source when establishing a subscriber link.
However, assuming you are accessing AMQP via a JMS client then as Petter says above, you should be able to use the browsing facility as defined by JMS I would imagine (I'm not familiar enough with camel to know how that is done).

Resources