Search code examples
javaosgiosgi-bundleblueprint-osgi

Terminating OSGI blueprint bundle after stop of referenced service


I have such issue. I have two OSGI blueprint bundles. One of them is like a service and another is using it. I'm running them on karaf. So, I want to implement functionality so when I'm stopping service then my other bundle also should be stopped. My xml's

    <?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd ">

    <reference id="weatherService" availability="mandatory" interface="com.myslyv4uk.weather.api.WeatherService" />  

    <bean id="showWeatherImpl" class="com.myslyv4uk.client.impl.ShowWeatherServiceImpl"
        init-method="start" destroy-method="stop" >
        <argument ref="weatherService" />
    </bean>

</blueprint>

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd ">

    <bean id="weatherServiceImpl" class="com.myslyv4uk.weather.impl.WeatherServiceImpl"
        init-method="start" destroy-method="stop" />

    <service ref="weatherServiceImpl">
        <interfaces>
            <value>com.myslyv4uk.weather.api.WeatherService</value>
        </interfaces>
    </service>  
</blueprint>

Java code skipped. I will just say that ShowWeatherService uses WeatherService to print random number. They both have start/stop method. I need to implement configuration or functionality in such way so after uninstall WeatherService bundle from karaf also ShowWeatherService was stopped. The problem is that I cannot do reference from WeatherService to ShowWeatherService because it will be cyclic reference it this bundles won't start up. What should I do? How could I terminate bundle from other bundle?


Solution

  • WARNING

    In this answer I explain how to stop a Blueprint bundle when a required service disappears. This code works as intended, however it is a bad idea for various reasons.


    You can register a listener on bind/unbind of a service, and act upon the removal of the service. This is an example on how to do it.

    <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
        xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd ">
    
        <bean id="referenceListener" class="your.ReferenceListener">
            <property name="bundleContext" ref="blueprintBundleContext"/>
        </bean>
    
        <reference id="weatherService"
                   availability="mandatory"
                   interface="com.myslyv4uk.weather.api.WeatherService">
            <reference-listener ref="referenceListener"
                                unbind-method="unbind" />
        </reference>
    
        <bean id="showWeatherImpl" class="com.myslyv4uk.client.impl.ShowWeatherServiceImpl"
            init-method="start" destroy-method="stop" >
            <argument ref="weatherService" />
        </bean>
    
    </blueprint>
    

    The referenceListener bean is called when the service is removed. By injecting the bundle context, you can stop the bundle itself:

    public class ReferenceListener {
    
    private Logger log; // set this up
    private BundleContext bundleContext;
    
    public void setBundleContext(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }
    
    // Called when the service is injected
    public void bind(ServiceReference<?> sr) {
        log.info("Bind of ServiceReference {} to bundle {}",
                sr, bundleContext.getBundle());
    }
    
    // Called when the service is removed
    public void unbind(ServiceReference<?> sr) {
        log.info("Unbind of ServiceReference {} from bundle {}",
                sr, bundleContext.getBundle());
        try {
            if (bundleContext.getBundle().getState() == Bundle.ACTIVE) {
                log.warn("Bundle {} will be stopped after unbind of mandatory ServiceReference {}",
                        bundleContext.getBundle(), sr);
                bundleContext.getBundle().stop();
            }
    
        } catch (BundleException e) {
            log.error("Cannot stop bundle {} after unbind of ServiceReference {}",
                    bundleContext.getBundle().getBundleId(),
                    sr,
                    e);
        }
    }
    }
    

    This solution works but has some drawbacks, for example if you restart the container the service is removed, thus setting the bundle in STOPPED state.