Search code examples
apache-camelapache-karafjbossfuseblueprint-osgi

How to deploy a camel/blueprint service multiple times using different property placeholders


Normally we are deploying our camel/blueprint based services once. Each service has its own property placeholder and the camel-context is bound to it:

<cm:property-placeholder id="service-name.placeholder" persistent-id="service-name.blueprint">
    <cm:default-properties>
        ...
    </cm:default-properties>
</cm:property-placeholder>
...
<camelContext id="service-name-service-camel" xmlns="http://camel.apache.org/schema/blueprint"
    useMDCLogging="true">
    <propertyPlaceholder id="properties" location="blueprint:service-name.placeholder" />
    <routeBuilder ref="mainRoute"/>
</camelContext>

Now we created a service that we want deploy multiple times. Each instance should use its own set of property-values. The only way i see is to set the property placeholder name on compile time (maven filter) but this will result in different artifacts - bad.

Is there a way to set the property placeholder to be used on runtime or start time?


Solution

  • You can do it with ManagedServiceFactory and a few lines of code.

    Define a bean for the Factory, and inject the BundleContext. Chose a Pid to later identify and configure this Factory:

    <bean id="myServiceFactory" class="org.my.MyServiceFactory" init-method="init" destroy-method="destroy">
        <property name="bundleContext" ref="blueprintBundleContext"/>
        <property name="configurationPid" value="org.my.pid"/>
    </bean>
    

    Implement the service Factory (not working code, just to give you an idea):

    public class MyServiceFactory implements ManagedServiceFactory {
    
        private BundleContext bundleContext;
    
        private String configurationPid;
    
        public void setConfigurationPid(String configurationPid) {
           this.configurationPid = configurationPid;
        }
    
        public void setBundleContext(BundleContext bundleContext) {
           this.bundleContext = bundleContext;
        }
    
        public void init() {
            // your setup goes here
        }
    
        public void destroy() {
            // your shutdown logic goes here
        }
    
        @Override
        public String getName() {
            return configurationPid;
        }
    
        @Override
        public void updated(String pid, Dictionary dict) throws ConfigurationException { 
            // Instantiate each service with its own properties
            MyServiceImpl service = new MyServiceImpl(dict);
            Dictionary servProps = new Properties();
            servProps.put("custom.service.property", "an id or someting")
            bundleContext.registerService(MyServiceImpl.class.getName(), service, servProps);
            // save your servicereferences to unregister, eg in a map
            // you can customize your service by giving some property to later retrieve it
        }
    
        @Override
        public void deleted(String pid) {
            // get the ServiceReference from some map
            servicereference.unregister();
        }
    
    }
    

    The ManagedServiceFactory has one method init() to setup all required resources, a destroy() method to clean up (for example by unregistering all the services).

    A new service instance is created for each config file in etc/org.my.pid-*.cfg, for example to create 3 instances of a service:

    etc/org.my.pid-serviceinstance1.cfg
    etc/org.my.pid-serviceinstance2.cfg
    etc/org.my.pid-whatever.cfg
    

    To get a particular instance of the service, register them with some custom property (like custom.service.property in my example). Then in a consumer bundle ask for an instance of MyService with custom.service.property = serviceinstance2 and you're done.

    You can even create new CamelContexts this way. There is a complete tutorial on PacktPub website.

    edit: When you write a new file in etc/org.my.pid-* the updated() method is called and a new service deployed. When a file is removed, the deleted() method is called and you must destroy and unregister the service. Of course, you can add/delete/modify files with JBoss/Karaf running :-) without stopping the main bundle.