Search code examples
osgiapache-felix

OSGi force bundle start twice with different configurations


I'm using embedded Felix in my application. Application can potentially deal with lot of plugins that exposes similar interface IFoo. There is default an implementation FooImpl Hopefully for most plugins default FooImpl can be used with specific configuration files.

I would like dynamically install and start the same bundle (with FooImpl) when new configuration file appears. I've reviewed already FileInstall but have no idea how to apply it there.

UPDATE: Deployment sequence. The jar containing FooImpl and IFoo is stable, but I need hot-deploy of new instances that are result of uploading new .cfg file to scope of FileInstall. So desired is very simple - user uploads .cfg, new service (instance of FooImpl) is appeared.


Solution

  • Using Factory Configurations would allow you to create different instances of FooImpl based on different configurations.

    For example in Declarative Services you can create a component like

    import org.apache.felix.scr.annotations.*;
    import org.apache.sling.commons.osgi.PropertiesUtil;
    
    @Component(metatype = true, 
            name = FooImpl.SERVICE_PID,
            configurationFactory = true, 
            specVersion = "1.1",
            policy = ConfigurationPolicy.REQUIRE)
    public class FooImpl implements IFoo
    {
        //The PID can also be defined in interface
        public static final String SERVICE_PID = "com.foo.factory";
    
        private static final String DEFAULT_BAR = "yahoo";
        @Property
        private static final String PROP_BAR = "bar";
    
        @Property(intValue = 0)
        static final String PROP_RANKING = "ranking";
    
        private ServiceRegistration reg;
    
        @Activate
        public void activate(BundleContext context, Map<String, ?> conf)
            throws InvalidSyntaxException
        {
            Dictionary<String, Object> props = new Hashtable<String, Object>();
            props.put("type", PropertiesUtil.toString(config.get(PROP_BAR), DEFAULT_BAR));
            props.put(Constants.SERVICE_RANKING,
                PropertiesUtil.toInteger(config.get(PROP_RANKING), 0));
            reg = context.registerService(IFoo.class.getName(), this, props);
        }
    
        @Deactivate
        private void deactivate()
        {
            if (reg != null)
            {
                reg.unregister();
            }
        }
    }
    

    Key points here being

    1. You use a component of type configurationFactory
    2. In the activate method you read the config and then based on that register a service
    3. In deactivate you explicitly unregister the service
    4. End users would then create config file with name <pid>-<some name>.cfg. Then DS would then activate the component.

    Then you can create multiple instances by creating configuration (using File Install like) file with name <pid>-<some name>.cfg like com.foo.factory-type1.cfg

    Refer to JdbcLoginModuleFactory and its associated config for one such example.

    If you want to achieve the same via plain OSGi then you need to register a ManagedServiceFactory. Refer to JaasConfigFactory for one such example.

    Key points here being

    1. You register a ManagedServiceFactory instance with configuration PID as the service property
    2. In the ManagedServiceFactory(String pid, Dictionary properties) callback register instances of FooImpl based on the config properties