Camel as an OSGI bundle - OSGI detection strategy

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Camel as an OSGI bundle - OSGI detection strategy

ddewaele
Hi,

I'm deploying Camel as an OSGI bundle (part of an EBA) on Websphere using the Spring OSGI extender.
When the bundle is started, the camelContext is started and the route is up and running.

I did notice however that it is not detecting that it is running in an OSGI environment and as such is not using the OsgiSpringCamelContext.

This results in issues like TypeConverter package scanning not working in the OSGI container.

Camel requires that the camel-spring bundle is STARTED before any bundles are started containing camel routes.

What is the preferred way of dealing with this start-order dependency ?

Reply | Threaded
Open this post in threaded view
|

Re: Camel as an OSGI bundle - OSGI detection strategy

Donald Whytock
On Thu, Nov 15, 2012 at 11:53 AM, ddewaele <[hidden email]> wrote:

> Hi,
>
> I'm deploying Camel as an OSGI bundle (part of an EBA) on Websphere using
> the Spring OSGI extender.
> When the bundle is started, the camelContext is started and the route is up
> and running.
>
> I did notice however that it is not detecting that it is running in an OSGI
> environment and as such is not using the OsgiSpringCamelContext.
>
> This results in issues like TypeConverter package scanning not working in
> the OSGI container.
>
> Camel requires that the camel-spring bundle is STARTED before any bundles
> are started containing camel routes.
>
> What is the preferred way of dealing with this start-order dependency ?

The preferred way in OSGI is having a BundleListener listen for a
STARTED event.  You'd put this in the bundles that are trying to
create routes.

http://www.osgi.org/javadoc/r4v43/core/org/osgi/framework/BundleListener.html

If you have a number of bundles dependent in this way, you might want
to create a bundle that does the listening for the camel-spring bundle
and publishes a service the other bundles submit custom listeners to.
That reduces, or at least centralizes, your dependence on extenal
events.

It's possible to do this using Camel without Spring also, but you'll
need to listen for the publishing of the appropriate endpoint module
services also.  Starting the Camel bundle isn't enough; some modules
start after and register themselves with camel-core.

Don
Reply | Threaded
Open this post in threaded view
|

Re: Camel as an OSGI bundle - OSGI detection strategy

ddewaele
Donald Whytock wrote
The preferred way in OSGI is having a BundleListener listen for a
STARTED event.  You'd put this in the bundles that are trying to
create routes.
As soon as my bundle containing the camel route is started, the Spring OSGI extender kicks in, and immediately refreshes my spring context (resulting in the creation of the non-osgi camel context if the camel-spring bundle wasn't started). I don't see a hook where I can influence this behavior.

Putting the listener in my bundle means that the bundle start event for camel-spring will be delivered when my bundle is starting (=too late).

Donald Whytock wrote
If you have a number of bundles dependent in this way, you might want
to create a bundle that does the listening for the camel-spring bundle
and publishes a service the other bundles submit custom listeners to.
That reduces, or at least centralizes, your dependence on extenal
events.
Do you have some sample code or a pointer to some documentation regarding this approach ?

Most of the camel osgi samples are being run on equinox / karaf , where there is some level of control of how and when bundles are started (as they are started from the console).

In the case of an EBA file, it's the container that loads them so there is lesser control (and these bundle start order dependencies should be avoided offcourse).

At the moment I don't see how this can be handled in a proper way.

Donald Whytock wrote
It's possible to do this using Camel without Spring also, but you'll
need to listen for the publishing of the appropriate endpoint module
services also.  Starting the Camel bundle isn't enough; some modules
start after and register themselves with camel-core.
We're currently using Spring, and cannot remove this dependency.

One downside of not having the OSGI based Camel Context is the fact that the TypeConverter package scanning doesn't work in OSGI. Other than that I didn't see any issues so far. But I can imagine further down the line, working with a non OSGI based Camel Context in an OSGI container will get us into trouble.

Reply | Threaded
Open this post in threaded view
|

Re: Camel as an OSGI bundle - OSGI detection strategy

Donald Whytock
On Fri, Nov 16, 2012 at 9:13 AM, ddewaele <[hidden email]> wrote:

> Donald Whytock wrote
>> The preferred way in OSGI is having a BundleListener listen for a
>> STARTED event.  You'd put this in the bundles that are trying to
>> create routes.
>
> As soon as my bundle containing the camel route is started, the Spring OSGI
> extender kicks in, and immediately refreshes my spring context (resulting in
> the creation of the non-osgi camel context if the camel-spring bundle wasn't
> started). I don't see a hook where I can influence this behavior.
>
> Putting the listener in my bundle means that the bundle start event for
> camel-spring will be delivered when my bundle is starting (=too late).

This depends on how you're starting your routes.  If you're starting
them with Spring XML, I personally can't help you, but others might.
But if you're starting them programatically, you can have the code
that starts the routes be triggered by a BundleListener event.  Your
bundle can start, and defer starting your routes until the event is
triggered.

> Donald Whytock wrote
>> If you have a number of bundles dependent in this way, you might want
>> to create a bundle that does the listening for the camel-spring bundle
>> and publishes a service the other bundles submit custom listeners to.
>> That reduces, or at least centralizes, your dependence on extenal
>> events.
>
> Do you have some sample code or a pointer to some documentation regarding
> this approach ?

Sorry, my project site was recently hacked.  I'll have to find my
archives.  Ping me in a week if I haven't responded, please.

> Most of the camel osgi samples are being run on equinox / karaf , where
> there is some level of control of how and when bundles are started (as they
> are started from the console).
>
> In the case of an EBA file, it's the container that loads them so there is
> lesser control (and these bundle start order dependencies should be avoided
> offcourse).

Bundle start order dependence is hard to avoid in osgi.  How I
understood it is that if you're dependent on an external bundle or
service you need to be sensitive to whether or not the dependency is
there or if it suddenly goes away.  I may have taken this a bit far in
some of my own code, but I got involved in OSGI and Apache Felix
specifically to give myself hot-swap capacity.

Don
Reply | Threaded
Open this post in threaded view
|

Re: Camel as an OSGI bundle - OSGI detection strategy

Donald Whytock
On Fri, Nov 16, 2012 at 1:17 PM, Donald Whytock <[hidden email]> wrote:
> Sorry, my project site was recently hacked.  I'll have to find my
> archives.  Ping me in a week if I haven't responded, please.

Okay...here's some hastily-thrown-together, untested code samples that
might nevertheless tell you what I mean.  It's a very barebones
example.

WaitForStart and NotifyOnStart are general-purpose interfaces to set
up multiple bundles to wait for a single bundle to activate them.  An
implemented WaitForStart accepts an OSGI ServiceReference to a
NotifyOnStart service that should be called when it's time for things
to start; the interface is structured so that the NotifyOnStart
implementation doesn't care what the WaitForStart's start condition
is, and the WaitForStart doesn't care what the NotifyOnStart will do
once started.

StartBundle implements WaitForStart and registers itself as a service
that other bundles can find.  It also implements BundleListener and
registers itself to receive BundleEvents.  When the desired bundle is
started, StartBundle will set a flag and call notifyOnStart() for each
active NotifyOnStart reference it has on file.  When a bundle calls
StartBundle's waitForStart(), StartBundle will either call the
supplied notifyOnStart() if the flag is set, or else store the
NotifyOnStart in a list to be called when the desired bundle is
started.

SampleApp implements NotifyOnStart and registers itself as a service
that can be passed to other bundles.  It also implements
ServiceListener and registers itself to receive ServiceEvents related
to instances of WaitForStart.  When a WaitForStart service is
registered, SampleApp fetches the WaitForStart service and calls
waitForStart(), passing its own ServiceReference.

Note that this example is specifically made for multiple bundles
waiting on a single event, and isolating the nature of that event from
them into a single coordinating bundle.  You can also just implement
BundleListener on some single bundle that sets up all your routes for
you.  Just wanted to provide the flexibility.

---------------------------

package org.mypackage.waitforstart.service;

import org.osgi.framework.ServiceReference;

public interface WaitForStart
  {
  public void waitForStart(ServiceReference<NotifyOnStart> startListener);
  }

------------------------

package org.mypackage.waitforstart.service;

public interface NotifyOnStart
  {
  public void notifyOnStart();
  }

----------------------

package org.mypackage.startbundle;

import org.mypackage.waitforstart.service.WaitForStart;
import org.mypackage.waitforstart.service.NotifyOnStart;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.ServiceReference;

import java.util.ArrayList;

public class StartBundle implements BundleActivator, BundleListener,
WaitForStart
  {
  ArrayList<ServiceReference<NotifyOnStart>> notifyList = new
ArrayList<ServiceReference<NotifyOnStart>>();
  String BUNDLENAME = "bundle-name";
  BundleContext context = null;
  boolean started = false;

  public void start(BundleContext context)
    {
    this.context = context;
    context.addBundleListener(this);
    context.registerService(WaitForStart.class.getName(), this, null);
    }

  public void waitForStart(ServiceReference<NotifyOnStart> notifyonstart)
    {
    if (started)
      {
      NotifyOnStart notifier = context.getService(notifyonstart);
      if (notifier != null)
        notifier.notifyOnStart();
      }
    else
      notifyList.add(notifyonstart);
    }

  public void bundleChanged(BundleEvent event)
    {
    NotifyOnStart notifier = null;

    if (event.getBundle().getSymbolicName().equals(BUNDLENAME))
      if (event.getType() == BundleEvent.STARTED)
        {
        started = true;

        for (ServiceReference reference: notifyList)
          {
          notifier = (NotifyOnStart) context.getService(reference);
          if (notifier != null)
            notifier.notifyOnStart();
          }
        }
    }

  public void stop(BundleContext context)
    {
    }
  }

-----------------------------

package org.mypackage.sampleapp;

import org.mypackage.waitforstart.service.NotifyOnStart;
import org.mypackage.waitforstart.service.WaitForStart;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;

public class SampleApp implements BundleActivator, NotifyOnStart,
ServiceListener
  {
  ServiceRegistration registration = null;
  BundleContext context = null;
  ServiceReference<NotifyOnStart> reference = null;
  boolean started = false;

  public void start(BundleContext context)
    {
    this.context = context;

    registration =
context.registerService(NotifyOnStart.class.getName(), this, null);
    reference = registration.getReference();

    context.addServiceListener(this, "(" + Constants.OBJECTCLASS + "="
+ WaitForStart.class.getName() + ")");
    }

  public void serviceChanged(ServiceEvent event)
    {
    if (event.getType() == ServiceEvent.REGISTERED)
      context.getService(event.getServiceReference()).waitForStart(reference);
    }

  public void notifyOnStart()
    {
    if (!started)
      /* start routes here */

    started = true;
    }

  public void stop(BundleContext context)
    {
    registration.unregister();
    }
  }