Search code examples
apache-camelblueprint-osgi

Camel Blueprint Testing - how can I dynamically replace/proxy a remote service referenced in Blueprint in order to prevent use of the real service?


I have the following scenario:

I have an OSGI bundle that has a service reference defined in the blueprint XML that references an interface in a remote bundle, and a bean that uses one of the impl's methods to populate a Properties object.

Relevant snippet from Bundle #1's XML (the consumer):

...
<!-- other bean definitions, namespace stuff, etc -->
<!-- reference to the fetching service -->
<reference id="fetchingService"    interface="company.path.to.fetching.bundle.FetchingService" />
<!-- bean to hold the actual Properties object: the getConfigProperties     method is one of the overridden interface methods -->
<bean id="fetchedProperties" class="java.util.Properties" factory-ref="fetchingService" factory-method="getProperties" />

<camelContext id="contextThatNeedsProperties" xmlns="http://camel.apache.org/schema/blueprint">

<propertyPlaceholder id="properties" location="ref:fetchedProperties" />

...
<!-- the rest of the context stuff - routes and so on -->

</camelContext>

Remote Bundle's blueprint.xml:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/blueprint"
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
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">


<cm:property-placeholder id="config-properties" persistent-id="company.path.configfetcher" />

<bean id="fetchingService" class="company.path.to.fetching.bundle.impl.FetchingServiceImpl" scope="singleton" init-method="createLoader" depends-on="config-properties">
    <property name="environment" value="${environment}" />
    <property name="pathToRestService" value="${restPath}" />
</bean>

<service ref="fetchingService" interface="company.path.to.fetching.bundle.FetchingService" />
<!-- END TESTING -->
</blueprint>

From Impl Class:

public synchronized Properties getProperties() {
    if(!IS_RUNNING) {
// timer task that regularly calls the REST api to check for updates
        timer.schedule(updateTimerTask, 0, pollInterval);
        IS_RUNNING = true;
    }
//Map<String, Properties> to return matching object if it's there 
    if(PROPERTIES_BY_KEY.containsKey(environment)) {
        return PROPERTIES_BY_KEY.get(environment);
    }
    /* if nothing, return an empty Properties object - if this is the case, then whatever bundle is relying on these
    *  properties is going to fail and we'll see it in the logs
    */
    return new Properties();
}

The issue:

I have a test class (extending CamelBlueprintTestSupport) and there are a lot of moving parts such that I can't really change the order of things. Unfortunately, the properties bean method gets called before the CamelContext is started. Not that big a deal because in the test environment there is no config file to read the necessary properties from so the retrieval fails and we get back an empty properties object [note: we're overriding the properties component with fakes since it's not that class being tested], but in a perfect world, I'd like to be able to do two things:

1) replace the service with a new Impl()

2) intercept calls to the getProperties method OR tie the bean to the new service so that the calls return the properties from the fake impl

Thoughts?

Edit #1:

Here's one of the things I'm doing as a workaround right now:

try {
ServiceReference sr =      this.getBundleContext().getServiceReference(FetchingService.class);

        if(sr != null) {
            ((FetchingServiceImpl)this.getBundleContext().getService(sr)).setEnvironment(env);
            ((FetchingServiceImpl)this.getBundleContext().getService(sr)).setPath(path);
        }

    } catch(Exception e) {
        log.error("Error getting Fetching service: {}", e.getMessage());
    }

The biggest problem here is that I have to wait until the createCamelContext is called for a BundleContext to exist; therefore, the getProperties call has already happened once. As I said, since in the testing environment no config for the FetchingService class exists to provide the environment and path strings, that first call will fail (resulting in an empty Properties object). The second time around, the code above has set the properties in the impl class and we're off to the races. This is not a question about something that isn't working. Rather, it is about a better, more elegant solution that can be applied in other scenarios.

Oh, and for clarification before anyone asks, the point of this service is so that we don't have to have a .cfg file for every OSGI bundle deployed to our Servicemix instance - this central service will go and fetch the configs that the other bundles need and the only .cfg file that need exist is for the Fetcher.

Other pertinent details:

Camel 2.13.2 - wish it was 2.14 because they've added more property-placeholder tools to that version that would probably make this easier

Servicemix - 5.3.1


Solution

  • Have you tried overriding CamelBlueprintTestSupport's addServicesOnStartup in your test (see "Adding services on startup" http://camel.apache.org/blueprint-testing.html)?

    In your case something like:

    @Override
    protected void addServicesOnStartup(Map<String, KeyValueHolder<Object, Dictionary>> services) {
        services.put(FetchingService.class.getName(), asService(new FetchServiceImpl(), null));
    }