I'm trying to use RabbitMQ with Camel. I am using Camel 2.14.1.
I want to open an fanout exchange on RabbitMQ and then later bind queues to it. This seems to work fine. However, everytime I create an Exchange, it is automatically bound to queue with a system name (a number). Can't I avoid that?
Here is a simple example which posts 100 messages to an Exchange. But they get delivered to an automatically created queue, I want to avoid this.
#Override
public void configure() throws Exception
{
final String testGUID = "xxxx";
from("timer://publish?repeatCount=100&period=10&fixedRate=true").process(new Processor()
//from("timer://publish?repeatCount=100&period=1&fixedRate=true").process(new Processor()
{
#Override
public void process(Exchange _exchange) throws Exception
{
String message = String.valueOf(_exchange.getProperty(Exchange.TIMER_COUNTER));
_exchange.getOut().setBody(message+testGUID);
}
})
.to("rabbitmq://localhost/exchange=logs1237?autoDelete=false&username=guest&password=guest&exchangeType=fanout");
}
Best regards,
Morten Knudsen
UPDATE:
It seems from looking at the source, that the triggering of the automatic queue happens if "queue" in RabbitMQEndPoint is not null. But "queue" is automatically assigned to "String.valueOf(UUID.randomUUID().toString().hashCode());" at construction.
If you don't want to bind the exchange with queue, you can setup the declare option to be false. BTW, declare option is new added since Camel 2.14.0.
As Bal has already described here add "declare=false" to your RabbitMQ URI. This should solve your problem.
Optionally, you can also use "skipQueueDeclare=true&skipQueueBind=true" this properties in your URI as well.
declare: If the option is true, camel declare the exchange and queue name and bind them together. If the option is false, camel won’t declare the exchange and queue name on the server.
skipQueueDeclare: If true the producer will not declare and bind a queue. This can be used for directing messages via an existing routing key.
skipQueueBind: If true the queue will not be bound to the exchange after declaring it
You can reach out all the properties you can use in Camel for RabbitMQ here.
From Camel 2.16.1 on, there's a new option for the rabbitmq component, skipQueueDeclare, which properly solves this issue.
Related
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();
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
Here is the route:
from("aws-sqs://myQueue?accessKey=RAW(xxx)&secretKey=RAW(yyy)&deleteAfterRead=false")
.log("Attributes: ${header.CamelAwsSqsAttributes}")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Map<String, String> messageAttributes = (Map<String, String>) exchange.getIn().getHeader("CamelAwsSqsAttributes");
...
}
});
The .log() shows an empty map as well as if I print messageAttributes from the processor.
I also tried with the header "CamelAwsSqsMessageAttributes" instead of "CamelAwsSqsAttributes" but still nothing.
I see the attributes from the AWS console though.
By the way I get the message body, and I use Camel 2.15
I figured it out, here is an example to get queue attributes and message attributes:
main.bind("sqsAttributeNames", Collections.singletonList("All"));
main.bind("sqsMessageAttributeNames", Collections.singletonList("All"));
Or add those objects to the registry if you don't use org.apache.camel.main.Main
Then:
from("aws-sqs://myQueue?accessKey=RAW(xxx)&secretKey=RAW(yyy)&deleteAfterRead=false&attributeNames=#sqsAttributeNames&messageAttributeNames=#sqsMessageAttributeNames")
Of course you can replace Collections.singletonList("All") with the list of attributes you need if you don't want all of them.
I faced the same issue. When I am using camel-aws 2.16.x and I have my endpoint configured as follow
from("aws-sqs://myQueue?...&messageAttributeNames=#sqsMsgAttributeNames")
.to(...)
Then I have defined a Collection of String in my spring configuration file
#Bean
public Collection<String> sqsMsgAttributeNames() {
return Arrays.asList("Attr1", "Attr2");
}
Above settings work fine but ever since I upgraded to camel-aws 2.17.3. It no longer works. As mentioned in Camel SQS Component, collection of string no longer will be supported for messageAttributeNames and it should be a String with attributes separated by comma.
Note: The string containing attributes should not contain any white
spaces otherwise camel-aws component will only read the first
attribute. I went through the pain to debug on this. Besides, setting the
attribute value to be "All" does not work for me, none of the message
attributes will be read.
Below is the changes I made that allowed camel-aws's SqsConsumer to work again:
#Bean
public String sqsMsgAttributeNames() {
return String.format("%s,%s", "Attr1", "Attr2");
}
It is not an issue of Camel. It can be the default behavior of SQS or aws-java-sdk-core library.
As a quick solution this aws-sqs URL can be used
aws-sqs://myQueue?<other attributes here>&attributeNames=All
Keep in mind that localstack can work well without attributeNames parameter, unlike SQS.
I am using Camel for my messaging application. In my use case I have a producer (which is RabbitMQ here), and the Consumer is a bean.
from("rabbitmq://127.0.0.1:5672/exDemo?queue=testQueue&username=guest&password=guest&autoAck=false&durable=true&exchangeType=direct&autoDelete=false")
.throttle(100).timePeriodMillis(10000)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
MyCustomConsumer.consume(exchange.getIn().getBody())
}
});
Apparently, when autoAck is false, acknowledgement is sent when the process() execution is finished (please correct me if I am wrong here)
Now I don't want to acknowledge when the process() execution is finished, I want to do it at a later stage. I have a BlockingQueue in my MyCustomConsumer where consume() is putting messages, and MyCustomConsumer has different mechanism to process them. I want to acknowledge message only when MyCustomConsumer finishes processing messages from BlockingQueue. How can I achieve this?
You can consider to use the camel AsyncProcessor API to call the callback done once you processing the message from BlockingQueue.
I bumped into the same issue.
The Camel RabbitMQConsumer.RabbitConsumer implementation does
consumer.getProcessor().process(exchange);
long deliveryTag = envelope.getDeliveryTag();
if (!consumer.endpoint.isAutoAck()) {
log.trace("Acknowledging receipt [delivery_tag={}]", deliveryTag);
channel.basicAck(deliveryTag, false);
}
So it's just expecting a synchronous processor.
If you bind this to a seda route for instance, the process method returns immediately and you're pretty much back to the autoAck situation.
My understanding is that we need to make our own RabbitMQ component to do something like
consumer.getAsyncProcessor().process(exchange, new AsynCallback() {
public void done(doneSync) {
if (!consumer.endpoint.isAutoAck()) {
long deliveryTag = envelope.getDeliveryTag();
log.trace("Acknowledging receipt [delivery_tag={}]", deliveryTag);
channel.basicAck(deliveryTag, false);
}
}
});
Even then, the semantics of the "doneSync" parameter is not clear to me. I think it's merely a marker to identify whether we're dealing with a real async processor or a synchronous processor that was automatically wrapped into an async one.
Maybe someone can validate or invalidate this solution?
Is there a lighter/faster/stronger alternative?
Or could this be suggested as the default implementation for the RabbitMQConsumer?
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).