19

I am working on a Java project using Camel & Spring. We would like to trigger an initialize method on a singleton bean after Spring finished doing its thing and Camel has finished building all routes.

We cant call the method at class creation time as it has dynamic linkings to other classes that it picks up from the @Component spring annotation and we dont know when/if these classes have been loaded yet to actually run the init method as part of a constructor.

How can I go about invoking a method or methods to run only once right after Camel startup is complete?

Thanks!

NightWolf
  • 7,694
  • 9
  • 74
  • 121

7 Answers7

35

another simple option which gives you a little more flexibility is to use camel-timer with a repeatCount=1 and a delay value long enough to let everything initialize. you can also add basic exception handling to delay/retry, etc...

from("timer://runOnce?repeatCount=1&delay=5000").to("bean:runOnceBean");
Ben ODay
  • 20,784
  • 9
  • 45
  • 68
  • Could you please provide sample code ? I'm struggling to get the from() API to be use in my class – PAA Sep 23 '16 at 17:55
  • 2
    Even this should work : from("timer://runOnce?repeatCount=1").to("bean:runOnceBean"); I don't think we need delay here, since if we are keeping this in a route it will be only called when context is ready. Please let me know if I'm thinking wrong somewhere. – Siddharth Sachdeva Oct 27 '16 at 07:31
  • correct, the delay isn't necessary unless you are waiting on something async to the context startup or want things to be primed, etc... – Ben ODay Nov 07 '16 at 22:03
11

If the bean must be invoked after CamelContext has started all the routes etc, then you can as Ben suggest use a route with a timer.

A possible better alternative is to use the EventNotifier API from Camel. And then invoke the logic on the CamelContextStartedEvent being fired. Some details on the EventNotifier API here: http://camel.apache.org/eventnotifier-to-log-details-about-all-sent-exchanges.html

Claus Ibsen
  • 56,060
  • 7
  • 50
  • 65
  • 2
    'PAGE NOT FOUND'... another example of the EventNotifier: https://people.apache.org/~dkulp/camel/eventnotifier-to-log-details-about-all-sent-exchanges.html – Marek Manduch Dec 09 '20 at 17:30
  • http://web.archive.org/web/20190126135728/https://camel.apache.org/eventnotifier-to-log-details-about-all-sent-exchanges.html was still alive as on 2019 – Eljah Jan 20 '21 at 20:37
3

One solution would be to patch a couple of files (see PR https://github.com/apache/camel/pull/684): CamelContextConfiguration.java and RoutesCollector.java.

In CamelContextConfiguration, add the method:

void afterApplicationStart(CamelContext camelContext);

And in onApplicationEvent of RoutesCollector add something like:

        if (camelContextConfigurations != null) {
            for (CamelContextConfiguration camelContextConfiguration : camelContextConfigurations) {
                camelContextConfiguration.afterApplicationStart(camelContext);
            }
        }

You may omit the if (camelContextConfigurations != null) if using the latest version as of this date.

Then create a Spring bean as follows to add your code:

@Bean
CamelContextConfiguration contextConfiguration() {
    return new CamelContextConfiguration() {

        @Override
        public void beforeApplicationStart(CamelContext camelContext) {
        }

        @Override
        public void afterApplicationStart(CamelContext camelContext) {
            // Put your code here
        }
    };
}

UPDATE: This pull request has been merged.

3

Add the logic in a method of your bean and annotate it with @PostConstruct - spring will invoke this method once this bean is fully initialized and all its dependencies are set.

@Component
class SomeClass {

 @PostConstruct
 void init() {
 }

}

If the logic needs to be invoked once the whole spring application context is fully initialized you can do that by implementing the LifeCycle interface.

gkamal
  • 20,777
  • 4
  • 60
  • 57
  • Thanks. Is `@PostConstruct` global in the sense that if I'm doing a lookup for all `@Component`s of a certain type using something like `ApplicationContent.getBeansOfType(SomeType.class)` will it always pick up instances of these components even if the order Spring loads them changes? – NightWolf Oct 10 '11 at 14:32
  • Is there a way around this without using the LifeCycle interface? For example with a bean you can do `` to call start() and stop() respectively... I was hoping there may be something like this for a Camel Context... Any change you have an example of how to use the `LifeCycle` interface? – NightWolf Oct 10 '11 at 14:41
  • @gkamal This won't work since the camelContext initialisation is NOT necessarily completed once the spring application context is completed. Hence, being invoked on post construct is no guarantee for the camel context being started and all routes being available. – Fritz Duchardt Sep 17 '15 at 11:18
2

You could use the startup order functionality in Camel documented at http://camel.apache.org/configuring-route-startup-ordering-and-autostartup.html :-

<route startupOrder="1" id="thisOneGoesFirst">    
  <from uri="seda:foo"/>
  <to uri="mock:result"/>
</route>
<route startupOrder="2" id="thisOneGoesSecond">
  <from uri="direct:start"/>
  <to uri="seda:foo"/>
</route> 
<route id="thisOneGoesLast">
  <from uri="direct:bar"/>
  <to uri="seda:bar"/>
</route>

where the routes with a startupOrder attribute will be executed in order and BEFORE all routes which have no startupOrder. So you can have your route with a timer consumer at which ever point you like, before or after your routes have been started.

mekondelta
  • 993
  • 7
  • 17
  • Suppose you define route "thisOneGoesFirst" with startupOrder="1" and with a "from" element that invokes the timer to start with no delay (ex "time:startup?repeatCount=1&delay=0"). Will the logic inside "thisOneGoesFirst" route be invoked by the timer before the logic inside the "thisOneGoesSecond" route? We know via the "startupOrder" that the timer will be invoked first, but my intuition is that the timer would simply invoke another thread to run the logic inside "thisOneGoesFirst" and whether that thread is invoked before the logic inside "thisOneGoesSecond" is a matter of randomness. – JohnC Jan 17 '20 at 21:41
1

You might try injecting the camel context into your singleton bean. The injection will not occur until the context is fully initialized ... including the building of all routes. The downside being that you might not actually require the context within your bean. I'm fiddling around in my head with an idea of linking the singleton bean dependency to the camelContext initialization in the spring configuration file, but not sure that will actually work.

Perception
  • 79,279
  • 19
  • 185
  • 195
  • 1
    This won't work since the camelContext initialisation is NOT necessarily completed once the spring application context is completed. – Fritz Duchardt Sep 17 '15 at 11:17
1

Like already hinted in the answers before this is rather a Spring than a Camel problem. In Spring you can simply implement InitializingBean and implement the menthod afterPropertiesSet. This is called when the wiring is done.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64