Tuesday, October 27, 2009

The Configuration Problem

Spring Dynamic Modules for Java has a nice feature to read configuration from the OSGi ConfigurationAdmin service and put them in a PropertyPlaceholderConfigurer to use in your spring contexts. However, there is a rather annoying problem with this support.

The problem is that the PropertyPlaceholderConfigurer is a BeanContextPostProcessor implementation, and therefore gets executed very early in the context creation. Too early in fact. Since the goal is to get configuration properties from the ConfigurationAdmin service, and expose those as a PropertyPlaceholderConfigurer, you would expect that you can simply add a dependency on the ConfigurationAdmin service to your context and have Spring-DM wait for ConfigurationAdmin to become available before processing the PropertyPlaceholderConfigurer. Unfortunately, the PropertyPlaceholderConfigurer is processed before the part where service dependencies are resolved, and it is therefore possible for the PropertyPlaceholderConfigurer to be processed before the ConfigurationAdmin service becomes available, yielding exceptions in your context creation like: could not resolve placeholder x. The standard way to get the PPC populated with ConfigurationAdmin properties is like:

   <!-- Configuration Admin entry -->
   <osgix:cm-properties id="cmProps" persistent-id="com.xyz.myapp">
      <prop key="host">localhost</prop>
   </osgix:cm-properties>

   <!-- placeholder configurer -->
   <ctx:property-placeholder properties-ref="cmProps" />

If ConfigurationAdmin does not happen to already be available in the OSGi container at the time the Spring context is created, the PPC will not be populated and the exceptions will start.

Waiting for ConfigurationAdmin

So how can we force Spring-DM to wait for the ConfigurationAdmin service to become available before processing the PPC? The first thought would be to create a different PPC (wrapping the original, possibly) that implements the org.springframework.osgi.context.DependencyAwareBeanFactoryPostProcessor interface. This would delay the processing of this new PPC until after Spring has resolved all the service dependencies in the context. There is a problem with this approach, however; it would preclude using any properties in the service dependencies themselves. So for instance, you could not use a property placeholder in a filter specification on a service dependency. I did try this, and it works fine, but I wanted to use properties in my filters so another solution was required.

The next thing that could be attempted would be to intercept the context creation before it really gets started and put a wait for ConfigurationAdmin at that point. Spring-DM provides a lot of customization potential, and this proves possible by creating a fragment bundle and providing an implementation of the OsgiApplicationContextCreator interface. This is easily done by extending Spring-DM's DefaultOsgiApplicationContextCreator class and overriding the createApplicationContext(BundleContext) method.

One way to override that method would be to wait for the ConfigurationAdmin service and then delegate to the super(BundleContext) method to create the context, thereby forcing ConfigurationAdmin to become available. However, this will not work because this part of Spring-DM is executed in the OSGi startup thread. This thread is synchronous, starting the context while blocking the container initialization. If ConfigurationAdmin is not available, and you wait for it, it will not become available because you have blocked the startup thread.

So this alone is not sufficient. What is needed is to get the wait for ConfigurationAdmin called by Spring-DM's asynchronous thread that is created to initialize the application context. We will need to create an extension of the OsgiBundleXmlApplicationContext class and have the OsgiApplicationContextCreator we supplied create an instance of this class. The in our custom OsgiBundleXmlApplicationContext class we can override a method that is called by the asynchronous Spring-DM initialization thread, allowing the OSGi startup thread to continue on starting other bundles. It turns out that overriding the prepareRefresh() method is called before the context is refreshed, and more importantly before the BeanFactoryPostProcessors like the PPC are processed.

Of course, it would be a bad idea to wait for ConfigurationAdmin at this point if the initialization of the context is to be done synchronously, so a check on this should be performed first. Then it is a simple matter to put some code in that method to wait for ConfigurationAdmin, then call the super() method and ConfigurationAdmin will be available for the PPC later in the initialization. A timeout on the ConfigurationAdmin wait would be a good idea to prevent waiting forever.

I have put this solution in place in my own use of Spring-DM and now the PPC evaluation always succeeds, even if the ConfigurationAdmin service is registered after the bundles using the PPC.

2 comments:

  1. Thanks for the suggestion Don. Spring DM 2.0 trunk contains a new configuration option for the config admin support to allow waiting until a non-null configuration is present.

    ReplyDelete
  2. I really like that new feature. It solves another problem of mine - waiting for configuration to show up. But it does not solve the problem I talk about here, which is that the CM service must be registered in the OSGi service registry before any spring-dm powered bundles using configuration admin properties are started.

    It would be possible to use start levels to ensure that CM is available first, but then that only solves part of the problem. CM could come and go (it is OSGi, after all) and that would affect any bundles needing it.

    ReplyDelete