Search code examples
javaapache-camelapache-karafblueprint-osgiapache-servicemix

Populate java.util.Properties from camel property placeholder


I have just started learning OSGi and camel and I am working on some services that are already implemented. I have a bundle that is configured to run on Servicemix with the help of OSGi blueprint somewhat like this:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camel-cxf="http://camel.apache.org/schema/blueprint/cxf"
xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/blueprint/core"
xsi:schemaLocation="
    http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
    http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
    http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd
    http://camel.apache.org/schema/blueprint/cxf http://camel.apache.org/schema/blueprint/cxf/camel-cxf.xsd">

<camelContext id="ctx1"
    xmlns="http://camel.apache.org/schema/blueprint"
    xsi:schemaLocation="http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
    <propertyPlaceholder location="properties/config.properties"/>
    <routeBuilder ref="..." />
</camelContext>

Currently, the config.properties is located inside the bundle but I am trying to externalize it.

So, I changed my blueprint to:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camel-cxf="http://camel.apache.org/schema/blueprint/cxf"
xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/blueprint/core"
xsi:schemaLocation="
    http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
    http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
    http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd
    http://camel.apache.org/schema/blueprint/cxf http://camel.apache.org/schema/blueprint/cxf/camel-cxf.xsd">

<camelContext id="ctx1"
    xmlns="http://camel.apache.org/schema/blueprint"
    xsi:schemaLocation="http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
    <propertyPlaceholder location="${karaf.home}/etc/config.properties"/>
    <routeBuilder ref="..." />
</camelContext>

... and this configuration works perfectly fine.

The problem I am facing is that this property file is also used in multiple locations via java.util.properties loaded as a simple file within a static code block in a util file.

Can I use the properties loaded inside camel context in my Java code (other than camel code)?

If that is not possible, then how should I load the property files located in servicemix classpath to be used in both camel context and java code, with minimal code changes in my current implementation.


Solution

  • I would discourage the use of static access to the Property file in a OSGI environment.

    For me the best way is to create an OSGI service which expose validation method or the properties themselves (with getter/setter) like this:

    <service ref="myConfig">
        <interfaces>
            <value>org.osgi.service.cm.ManagedService</value>
            <value>com.mycompany.services.common.api.MyConfig</value>
        </interfaces>
        <service-properties>
            <entry key="service.pid" value="config.properties" />
        </service-properties>
    </service>
    
    <bean id="myConfig" class="com.mycompany.services.common.config.MyConfigImpl" />
    

    After in any OSGI bundle which needs to access the property, it can referenced by:

    <reference id="myConfig" interface="com.mycompany.services.common.api.MyConfig"
        activation="eager" availability="mandatory" />
    

    Before using this way we had a lot of deadlocks between bundles because the usage of static method and LOCK's. Since we use the OSGI service, it work very well without problem when refreshing / deploying the bundles.

    I assume you want to access your property file through any other OSGI bundle, not external code from the OSGI context. If you want to access the property file outside the OSGI context, I suggest to create a (REST) Web-service over the mentioned interface (<reference>).

    Implementation example:

    public class MyConfigImpl implements MyConfig, ManagedService {
    
        // This is just the property keys 
        private final static String COLORS = "my.valid.colors";
        private final static String PROP1 = "my.property.1";
        private final static String PROP2 = "my.property.2";
        private final static String PROP3 = "my.property.3";
    
        // For validating against some properties
        private List<String> colors = new ArrayList<>();
    
        // For extracting a subset of properties
        private String prop1;
        private String prop2;
        private String prop3;
    
        // The whole set of properties published as Dictionary (could be transformed in Map as well)
        private Dictionary props;
        
        @Override // Implements MyConfig.isValidColor(String color)
        public Boolean isValidColor(String color) {
            if (colors.contains(color)) {
                return true;
            } else {
                return false;
            }
        }
    
        @Override // Implements MyConfig.getPropertyOne()
        public String getPropertyOne(){
            return prop1;
        }
    
        @Override // Implements MyConfig.getPropertyTwo()
        public String getPropertyTwo(){
            return prop2;
        }
    
        @Override // Implements MyConfig.getPropertyThree()
        public String getPropertyThree(){
            return prop3;
        }
    
        @Override // Implements MyConfig.getProperties()
        public Dictionary getProperties(){
            return props;
        }
    
    
        // This implements the ManagedService.updated()
        @Override 
        public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
            
            log.debug("Reading properties: {}", properties);
            if (properties == null) {
                return;
            }
    
            updateConfiguration(properties);
    
        }
    
        private void updateConfiguration(Dictionary properties) {
    
            // MyUtil.asListOfString is just a helper to convert comma separated string to list of String
            // This is just an example
            this.colors = MyUtil.asListOfStrings((String) properties.get(COLORS));
    
            this.prop1 = properties.get(PROP1);
            this.prop2 = properties.get(PROP2);
            this.prop3 = properties.get(PROP3);
            this.props = properties;
        }
    }