Camel ActiveMQ how to destroy/purge a virtual topic queue without consumers - apache-camel

My application consists of many OSGi bundles running inside JBoss Fuse 6.2.1. Each bundle has a Camel route consuming from an ActiveMQ endpoint. Data is exchanged using VirtualTopics.
ProducerBundle publishes to topic VirtualTopic.MyTopic
ConsumerBundle A consumes from queue Consumer.A.VirtualTopic.MyTopic
ConsumerBundle B consumes from queue Consumer.B.VirtualTopic.MyTopic
ConsumerBundle C consumes from queue Consumer.C.VirtualTopic.MyTopic
At a certain moment in time Consumer C is closed, its bundle uninstalled and will never come back. Howewer, messages are still enqueued into Consumer.C.VirtualTopic.MyTopic queue.
How do I destroy such queue?
ActiveMQ pauses the Producer when the queue fills up, and I cannot set a small time to live on the message as other consumers may take a while to process each message. I cannot modify the VirtualTopic structure. I have full access to
ActiveMQ configuration and Camel routes.
Are there any other options to handle the situation?
<!-- producer route -->
<route id="ProducerRoute"/>
<from uri="direct:trigger"/>
<to uri="activemq:topic:VirtualTopic.MyTopic"/>
</route>
<!-- each consumer route -->
<route id="ConsumerARoute">
<from uri="activemq:Consumer.A.VirtualTopic.MyTopic"/>
<to uri="bean:myProcessor"/>
</route>

Look at the Apache ActiveMQ documentation how you can delete inactive queues/topics such as: http://activemq.apache.org/delete-inactive-destinations.html

I went for the aggressive solution: I hook into OSGi bundle lifecycle, when it is stopped I use JMX MBeanServer to destroy the now unneeded queues.
Since my bundle is managed using blueprint, I opted for a bean with a destroy method.
Here's an example implementation:
My bean
package org.darugna.osgi;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class QueueDestroyer {
private static final String[] QUEUES_TO_DESTROY = {
"Consumer.A.VirtualTopic.MyTopic"
};
private MBeanServer mBeanServer;
public void setMbeanServer(MBeanServer mBeanServer) {
this.mBeanServer = mBeanServer;
}
public void destroy() throws Exception {
ObjectName brokerName = new ObjectName("org.apache.activemq:type=Broker,brokerName=amq");
for (String queueName : QUEUES_TO_DESTROY) {
Object returnValue = mBeanServer.invoke(brokerName,
"removeQueue",
new Object[]{queueName},
new String[]{String.class.getName()});
}
}
}
Blueprint.xml
<blueprint>
<reference id="mbeanServer" interface="javax.management.MBeanServer"
availability="mandatory"/>
<bean id="queueDestroyer" class="org.darugna.osgi.QueueDestroyer"
destroy-method="destroy">
<property name="mbeanServer" ref="mbeanServer"/>
</bean>
<camelContext>
<route>
<from uri="activemq:Consumer.A.VirtualTopic.MyTopic"/>
<to uri="bean:myProcessor"/>
</route>
<camelContext>
</blueprint>

I had a similar situation, but I couldn't use the Claus's suggestion because in my broker there are other queues that have no consumer and I doesn't want to delete them.
In my case I'm running a JBoss Fuse 6.1.0 with fabric (I think that is the same with newer version of Fuse): I just removed the consumer (in my case I just removed the profile with the consumer) and after that I deleted the queue with the delete button in the hawtio console.

Related

Spring Boot Camel Test

I am writing a test for Camel using Spring boot.
Below is configuration on the test class
#RunWith(CamelSpringBootRunner.class)
#SpringBootApplication
#ComponentScan(basePackages = ["example.test"])
#UseAdviceWith
#BootstrapWith(SpringBootTestContextBootstrapper)
#DirtiesContext
class RouteTest {
private static final Logger LOGGER = LoggerFactory.getLogger(RouteTest.class)
#Autowired ModelCamelContext camelContext
#Test
void "flow"() {
camelContext.getRouteDefinition(route.routeId).adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
void configure() throws Exception {
}
}
LOGGER.info("IN TEST: ******* Camel Status: "+camelContext.getStatus())
}
I expect camel should not be started. But when I run the test it is already started.
I noticed that CamelSpringBootRunner does start camel context in CamelSpringBootExecutionListener.
How do I force not to start the camel context.
In the latest version of camel there is an option for autoStartup of camel. You an achieve what you want by adding autoStartup option.
For example the route below is configured autoStartup=false to prevent Camel starting when Spring starts.
<camelContext id="myCamel" xmlns="http://camel.apache.org/schema/spring" autoStartup="false">
<route>
<from uri="direct:start"/>
<to uri="mock:result"/>
</route>
</camelContext>
You can manually start Camel later by invoking its start method as shown below:
ApplicationContext ac = ...
SpringCamelContext camel = (SpringCamelContext) ac.getBean("myCamel");
// now start Camel manually
camel.start();
If you are using older version of camel then autoStartup option will not work try using shouldStartContext instead.
Sometimes starting camel after setting shouldStartContext doesn't work so I have put the work around in below example. Try this :
setting shouldStartContext manually before starting the context from the code:
((SpringCamelContext)camelContext).setShouldStartContext(true);
camelContext.start();
Example context:
<camel:camelContext id="ids.camel.context" shouldStartContext="false">
<!-- Queue endpoints. -->
<camel:endpoint id="defaultInQueue" uri="jms:queue:${default.in.queue.name}"/>
<camel:endpoint id="defaultOutQueue" uri="jms:queue:${default.out.queue.name}"/>
<!-- Route to send messages to IDS -->
<camel:route id="out" trace="true">
<camel:from uri="direct:sender"/>
<!-- Do not expect answer (fire and forget) -->
<camel:setExchangePattern pattern="InOnly"/>
<camel:to ref="defaultOutQueue"/>
</camel:route>
<!-- Route to receive -->
<camel:route id ="in" trace="true">
<camel:from ref="defaultInQueue"/>
<camel:to uri="bean:defaultTextAdapter?method=onMessage"/>
</camel:route>
</camel:camelContext>
Using Camel 2.20.1 solved the issue

Camel JMS component, how to get a callback on subscriptions?

I am developing a service where I need to subscribe to JMS Queues dynamically with Camel JMS 2.17.0.
So I create a listening route at runtime using a bean:
// Bean: createCustomListenerNow
String endpoint = "jms:queue:" + queueName +
"&testConnectionOnStartup=true&exceptionListener=#listenerBuilder";
// camelContext.addRoutes uses this RouteBuilder
#Override
public void configure() throws Exception {
from(endpoint)
.id(id)
.to("seda:processReply");
}
My actual broker is Websphere MQ and I may get an exception if my account doesn't have the right permissions. Unfortunately this happens after my route has been built.
My route goes like this:
<route>
<from uri="direct:mock" />
<to uri="bean:createCustomListenerNow" /> <!-- I want to block here until subscription is OK -->
<to uri="bean:doSomethingThatRequiresTheListenerToBeProperlyRunning" />
</route>
Is there a way to "block" until the JMS component creates a new connections and successfully subscribes? I didn't manage to find a callback or a simple way to get this information.
What I do now is registering a javax.jms.ExceptionListener and waiting for a JMSException. If the exception is not thrown before a timeout I suppose the subscription did succeed.
I don't think this approach is rock-solid, any suggestion is appreciated.
I cannot block until I receive a message on the listener because it can arrive hours later and I need to finish processing the current Exchange with a reply like "Setup of listener is ok" or "Setup of listener failed".

Camel: communicating between two routes

I'm basically new to camel. I set up a camel context with two routes that are using seda endpoints.
Simplyfying, all starts with a "from" file endpoint (sorry for the terminology if wrong) listening on a directory:
<route>
<from uri="file:mydir"/>
<process ref="a bean that change the body of the message by setting a custom object"/>
<to uri="seda:incoming"/>
</route>
<route>
<from uri="seda:incoming"/>
<process ref="a bean that does something with the custom object above"/>
....
</route>
now, what described above works perfectly but i need to change seda with activemq queues and after doing that the body of the message received by the 2nd processor is empty.
How can I obtain the same behaviour of seda endpoints using activemq channels?
exchange.getIn().setBody(myCustomBean)
and
exchange.getIn().setHeader("inputfile", aFileInstance)
If you expect to get some result when aquiring from activemq queue, you should send serializable object to queue. Otherwise object will not be transferred.
At your case there's no guarantee that myCustomBean and aFileInstance are serializable.
Basically try sending the Strings into the queue. Or make your objects serializable.

Apache Camel Endpoint injection to direct route "No consumers available on endpoint"

I want to use Camel to take a message from ActiveMQ and then, based on the message contents (a protobuf), send one or more messages to Twitter. I've written a bean that is called from within a route and which uses injection to send multiple messages to a "direct:xyz" endpoint.
However, Camel is complaining at runtime that:
2012-11-16 09:56:33,376 | WARN | ication.twitter] | DirectProducer | 160 - org.apache.camel.camel-core - 2.10.2 | No consumers available on endpoint: Endpoint[direct://twitter] to process: Exchange[Message: hello world]
If I instead inject directly to the Twitter endpoint from within the bean, it works fine. However, in order to ease testing, simplify configuration etc, I'd like to keep the actual Twitter config separate, hence wanting to send to a separate route.
The camel context config looks like:-
<camelContext id="NotificationTwitter"
trace="false" xmlns="http://camel.apache.org/schema/blueprint">
<dataFormats>
<protobuf id="notificationProto" instanceClass="org.abc.schemas.protobuf.NotificationDef$NotificationMsg" />
</dataFormats>
<route id="TwitterPreparation">
<from uri="activemq:notification.twitter" />
<unmarshal ref="notificationProto" />
<log logName="abc" loggingLevel="INFO"
message="Twitter request received: ${body}" />
<bean ref="NotificationTweeter" method="createTweets" />
</route>
<route id="Twitter">
<from uri="direct:twitter" />
<log logName="abc" loggingLevel="INFO"
message="Tweeting: ${body}" />
<to uri="twitter://timeline/user?consumerKey=itsasecret&consumerSecret=itsasecret&accessToken=itsasecret&accessTokenSecret=itsasecret" />
</route>
</camelContext>
The bean looks like:-
public class NotificationTweeter {
#EndpointInject(uri = "direct:twitter")
private ProducerTemplate producerTemplate;
public void createTweets(NotificationMsg notification) {
String tweet = notification.getMessageDetail().getTitle();
try {
// only send tweets where the notification message contains the Twitter mechanism
for (MechanismMsg mechanism : notification.getMechanismList()) {
if (mechanism.getType() == MechanismTypeEnum.TWITTER) {
// Cycle round the recipients
for (RecipientMsg recipient : mechanism.getRecipientList()) {
tweet = "#" + recipient.getIdentifier() + " " + tweet;
producerTemplate.sendBody(tweet);
}
// TODO exceptions if no recipients found, etc
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
I've had this problem in other routes (it's certainly not related to the Twitter feature) but have just worked around it. This time, however, I'd like to actually understand what the issue is! Any help gratefully received, thanks.
According to your setup, it might also depend on the CamelContext you have picked up. I got the same error message because I was sending messages on a route that existed in another CamelContext than the one I actually was using.
(Although the previous answer was already accepted, this might be the working solution for other people searching for that error message.)
It sounds like a problem with the startup ordering of your routes. See more detail here http://camel.apache.org/configuring-route-startup-ordering-and-autostartup.html
You can configure the "direct" route to start before the other route, then that issue should be resolved.
For others coming here, this error can also be caused by an OSGI error for a dependency that has not been deployed.
A bit late to the party but this error happened to me when I had two separate blueprint files, one for normal running and one for test. In my test I was referring to the test blueprint but noticed that the normal one was also automatically started which caused errors.
In the documentation http://camel.apache.org/blueprint-testing.html it says you can disable certain bundles from starting up. That helped me in my case.
This can also be caused by having a . in the route name. Replace my.Route.Name with myRouteName fixed the issue for me.

Loading a Service on startup of the ServiceMix 4.3 which consumes another service using ActiveMQ/Apache Camel

We are trying with ServiceA calling ServiceB as soon as bundle loads during the SericeMix startup. Service2 having activemq endpoints we need to invoke a method of that particular service. I tried by spring init-method attribute in the bean tag which helps in auto trigger a method in ServiceA in that method I am calling the serviceB’s method. I am getting Exception like No Consumer available for the endpoint. I assume that as soon as the Service1 is up it is not getting the instance of the service2 which needs to get initialized using #Produce annotation activemq endpoint. The same services work fine in the other normal scenarios.
Exception:
Caused by: org.apache.camel.CamelExchangeException: No consumers available on endpoint: Endpoint[direct://ServiceB]. Exchange[Message: BeanInvocation public java.lang.String java.lang.Object.toString() with null]]
at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:46)
at org.apache.camel.component.bean.CamelInvocationHandler.invoke(CamelInvocationHandler.java:64)
... 35 more
I am copy pasting the Code Block for your reference.
public class ServiceA{
#Produce(uri = "direct:ServiceB") //Active MQ endpoint
private ServiceB serviceB;
public void start()
{
Object obj = serviceB.getData(); }
. . .
.....
}
}
**bundle-context.xml**
//Changes for method to auto trigger during spring bean load
<bean id="serviceA" class="com.test.serviceA" init-method="start">
</bean>
**bundle-context-camel.xml**
<osgi:camelContext id="ServiceA"
xmlns="http://camel.apache.org/schema/spring">
<template id="producerTemplate" />
<!-- These routes are outbound to other services -->
<route>
<from uri="ServiceB" />
<bean ref="enrichOutboundExchangeRef" />
<to uri="activemq:ServiceB?transferException=true" />
</route>
..............
</osgi:camelContext>
Or is their anyother way if i need to achieve this requirement? where i can load a service(consumes other services) automatically during the servicemix bootup.
You can use seda instead of direct as it's queue based and thus consumers can come and go.
Also try using springs depends-on attribute
<bean id="serviceA" depends-on="myCamel" .../>
<osgi:camelContext id="myCamel" ...>
We tried the above approach but we still we are getting exceptions, we resolved it by adding a listener to the onCamelContextStarted() during th init of the serviceA.
Thanks
Ravi
If you are getting "no consumers available on endpoint", it means that messages are being routed to an endpoint that hasn't been initialized. I recommend decoupling the services using a JMS queue between them. That way serviceA can put messages in the queue (independent of serviceB's availability) and serviceB can then act as a polling consumer against that queue whenever its ready.

Resources