Resolving Startup Order of Carbon Components in WSO2 Carbon 5.0.0

In my previous post https://medium.com/@sameera.jayasoma/startup-order-resolving-mechanisms-in-osgi-48aecde06389, I explained the startup order resolving mechanisms available in OSGi. Please read that post first, if you haven’t read it.

The Problem

While developing WSO2 Carbon based products, we realized that the default startup order resolving mechanisms in OSGi are not enough. Let me explain why? Consider the example from the previous post.

Microservice engine and Transport manager can be considered as Carbon components. Both these components contain one or more related OSGi bundles. Transport manager should be initialized after the microservice engine. Why? Simply because the transport manager should not open any ports until all the microservices are deployed properly. This can be easily handled via OSGi services. Microservice engine registers an OSGi service when it is fully initialized and the transport manager gets activated soon after that service is available.

Things get a little bit complicated when you think about other dependencies of microservice engine and transport manager. Transport manager has to wait all the transport services are registered from individual bundles during the server startup. E.g. HTTPS transport bundle registers a transport as an OSGi service. In the above figure, there are 5 such transport bundles. During the server startup, we can expect 5 different OSGi services of type transport. Now the transport manager has to wait until all 5 OSGi services are available. How can we achieve this in OSGi?

Here transport manager does not need to wait for OSGi services of type transport which come from transport bundles which get installed after the server startup. Transport manager only waits for OSGi services of type transport which become available during the server startup.

What happens If someone adds a new transport or if someone removes an existing transport bundle? Now you realize that number of transport OSGi services is not static, we have to calculate the expected number of transport services at runtime. How can we do this? OSGi declarative services do not solve this problem.


Solution

In order to solve this problem, we need to know the expected number of OSGi services of type transport. Expected number of services has to be calculated using static resources such as the MANIFEST.MF file of OSGi bundles or any other files inside OSGi bundles. Once you know the expected number of OSGi services, you can hold the initialization of transport manager until all those transport services become available at runtime.

One can argue that this breaks the dynamism of OSGi because OSGi services can come and go at any time and depend on a specific number of OSGi services is tricky. But in WSO2, we develop middleware products and those are server-side components. We need to resolve the startup order of our components. These specific requirements cannot be satisfied using the default mechanisms in OSGi. Let me explain why.

We’ve recently introduced a framework called msf4j to develop microservices. You can develop and run microservices in the standalone mode and in WSo2 Carbon based products as OSGi bundles. If you are using Carbon based products to host your microservices, then you need to register your microservices as OSGi services. Now when the microservices engine receives a request, engine dispatches it to the correct microservice. Now here is the problem, microservices engine cannot accept requests until it knows that all the microservices are available as OSGi services. Otherwise during server startup, certain microservices will be available while others microservices are yet to be registered as OSGi services. We shouldn’t open ports until all the microservices are available. Microservices engine should notify when all the microservices are registered as OSGi services during the start. Then only transport manager should open ports. All this should happen during the server startup. How can you solve this problem in OSGi?


Startup Order Resolver

Startup Order Resolver component is designed to handle startup ordering complexities in WSO2 Carbon based products. This component is available from WSO2 Carbon kernel 5.0.0. This is a generic utility which can resolve startup order of any other component such as microservice engine, transport manager etc. In the current version, Startup order resolver handles two types of components.

  1. Components which register OSGi services. E.g. user-mgt components exposes it’s capabilities via a microservice. JMS transport module registers JMS transport as an OSGi services. We can call such components as “OSGi service components”.
  2. Components which needs to hold it’s initialization until all the required OSGi services or capabilities are available during the server startup. E.g. Microservice engine needs to hold it’s initialization until all the microservices from OSGi bundles are registered as OSGi services. We can call such components as “startup listener components”.

Startup Listener Component

Startup order resolver identifies a startup listener component from the following manifest header.

Carbon-Component: startup.listener;componentName=”transport-mgt”;requiredService=”org.wso2.carbon.kernel.transports.CarbonTransport”

startup.listener — Marks this components as a startup listener component.

componentName — Unique name to identify this component. Each and every startup listener component should defines a unique name.

requiredService — Comma separated list of OSGi service keys. This startup listener component should hold it’s initialization till all the services of specified keys are available.

Now how does the startup order resolver notifies a startup listener component when all the required services are available? In order to get this notification, startup listener component should register an OSGi service of the interface RequiredCapabilityListener from package org.wso2.carbon.kernel.startupresolver. Have a look at the following code segment. You need to register this service with following service property name.

componentName — The value of this property should be equal to the value of the componentName property of the startup listener component that I mentioned earlier. This is how the startup order resolver maps the RequiredCapabilityLister with corresponding startup listener component (startup.listener).

public class TransportStartupListener implements RequiredCapabilityListener {

@Override
public void onAllRequiredCapabilitiesAvailable() {
// This method is invoked by the startup
order resolver when all the required
services are available.
}
}

Here is how you can register above RequiredCapabilityListener as an OSGi service in your BundleActivator.

public class TransportBundleActivator implements BundleActivator {
public void start(BundleContext bundleContext)
throws Exception {

Dictionary<String, String> properties = new Hashtable<>();
properties.put("componentName", "transport-mgt");
        bundleContext.registerService(
TransportStartupListener.class,
new TransportStartupListener(), properties);
}

public void stop(BundleContext bundleContext) throws Exception {

}
}

OSGi Service Component


Startup order resolver identifies an OSGi service component from the following manifest header.

Please note that all the components which register OSGi services do not need to include this manifest header. You need to include this header only if there are other components which wait for your OSGi service. E.g. Transport manager component waits for all the transport OSGi services which means If you are developing a transport, you need to put this header into your bundle’s MANIFEST.MF.
Carbon-Component: osgi.service; objectClass=”org.wso2.carbon.kernel.transports.CarbonTransport”

osgi.service — Marks this component as an OSGi service component

objectClass — This component registers an OSGi service of this type.

Advanced usecase — Now what if you register more than one OSGi services of same type from your bundle. You can use the serviceCount manifest attribute to specify the number of services that you register from your bundle. Here you know the exact number of services that your register at the development time.

Carbon-Component: osgi.service; objectClass=”org.wso2.carbon.kernel.transports.CarbonTransport”; serviceCount="4"

Advanced usecase — In certain scenarios, at the development time, you may not know the number of OSGi services you register from your bundle. But during the server startup, you can calculate a number of OSGi services you need to register. You may obtain this value from a configuration file, from another OSGi service or from any source. Now how do you specify the service count? Obviously, you cannot use the serviceCount manifest attribute, since you don’t know the count at development time.

In order to solve this problem, we have introduced an interface called CapabilityProvider from package org.wso2.carbon.kernel.startupresolver. You can register an OSGi service of type CapabilityProvider to specify the count during the server startup. You need to register this service with following service property name.

capabilityName —Type of the OSGi service that this CapabilityProvider provides.

public class HTTPTransportProvider implements CapabilityProvider {

@Override
public int getCount() {
return 4;
}
}

Now you need specify the Carbon-Component manifest header as follows.

Carbon-Component: osgi.service; objectClass=”org.wso2.carbon.kernel.startupresolver.CapabilityProvider”;capabilityName="org.wso2.carbon.kernel.transports.CarbonTransport"

capabilityName — Type of the OSGi service that this CapabilityProvider provides.

Here is how you can register above CapabilityProvider as an OSGi service in your BundleActivator.

public class HTTPBundleActivator implements BundleActivator {
    public void start(BundleContext bundleContext)
throws Exception {

Dictionary<String, String> properties = new Hashtable<>();
properties.put("capabilityName",
"org.wso2.carbon.kernel.transports.CarbonTransport");
        bundleContext.registerService(
HTTPTransportProvider.class,
new HTTPTransportProvider(), properties);
}

public void stop(BundleContext bundleContext) throws Exception {

}
}

As you can see now, startup order resolver process all the “Carbon-Component” manifest headers and figures out which components need to be notified when their requirements are satisfied. Similarly startup order resolver figures out the expected number of OSGi services of each and each required services of startup listener components. Then startup order resolver listens to OSGi service events and notifies startup listener components as and when their requirements can satisfied.

Show your support

Clapping shows how much you appreciated Sameera Jayasoma’s story.