FELIX-1165 Ensure setting the service reference field for a ManagedService
configuration also during update.
Additional fixes:
* NPE prevention if bundle event is handled after bundle stop (due to timing)
* logging if on configuration update multiple ManagedService candidates exist
* integration test for service reference field setting
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@804387 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
index 82e5b16..1bd31ff 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
@@ -138,7 +138,8 @@
private Map factories;
// the cache of Configuration instances mapped by their PID
- private Map configurations;
+ // have this always set to prevent NPE on bundle shutdown
+ private final Map configurations = new HashMap();
// the maximum log level when no LogService is available
private int logLevel = CM_LOG_LEVEL_DEFAULT;
@@ -173,7 +174,6 @@
// set up some fields
this.bundleContext = bundleContext;
this.factories = new HashMap();
- this.configurations = new HashMap();
// configurationlistener support
configurationListenerTracker = new ServiceTracker( bundleContext, ConfigurationListener.class.getName(), null );
@@ -205,8 +205,7 @@
bundleContext.addBundleListener( this );
// get all persistence managers to begin with
- pmtCount = 1; // make sure to get the persistence managers at least
- // once
+ pmtCount = 1; // make sure to get the persistence managers at least once
persistenceManagerTracker = new ServiceTracker( bundleContext, PersistenceManager.class.getName(), null );
persistenceManagerTracker.open();
@@ -227,6 +226,9 @@
public void stop( BundleContext bundleContext )
{
+ // stop handling bundle events immediately
+ handleBundleEvents = false;
+
// immediately unregister the Configuration Admin before cleaning up
// clearing the field before actually unregistering the service
// prevents IllegalStateException in getServiceReference() if
@@ -246,7 +248,6 @@
// stop listening for events
bundleContext.removeBundleListener( this );
- handleBundleEvents = false;
if ( configurationListenerTracker != null )
{
@@ -274,8 +275,13 @@
logTracker.close();
}
+ // just ensure the configuration cache is cleared, not cleaning
+ synchronized ( configurations )
+ {
+ configurations.clear();
+ }
+
this.bundleContext = null;
- this.configurations = null;
}
@@ -942,8 +948,7 @@
}
// 104.3 Report an error in the log if more than one service
- // with
- // the same PID asks for the configuration
+ // with the same PID asks for the configuration
if ( cfg.getServiceReference() != null && !sr.equals( cfg.getServiceReference() ) )
{
log( LogService.LOG_ERROR, "Configuration for " + pid + " has already been used for service "
@@ -1189,17 +1194,31 @@
{
if ( config.getFactoryPid() == null )
{
- ServiceReference[] sr = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
- + Constants.SERVICE_PID + "=" + config.getPid() + ")" );
- if ( sr != null && sr.length > 0 )
+ final ServiceReference[] srList = bundleContext.getServiceReferences( ManagedService.class
+ .getName(), "(" + Constants.SERVICE_PID + "=" + config.getPid() + ")" );
+ if ( srList != null && srList.length > 0 )
{
- ManagedService srv = ( ManagedService ) bundleContext.getService( sr[0] );
+ final ServiceReference sr = srList[0];
+ final ManagedService srv = ( ManagedService ) bundleContext.getService( sr );
+
+ // 104.3 Report an error in the log if more than one service
+ // with the same PID asks for the configuration
+ if ( srList.length > 1 )
+ {
+ for ( int i = 1; i < srList.length; i++ )
+ {
+ log( LogService.LOG_ERROR, "Configuration for " + config.getPid()
+ + " is used for service " + sr
+ + "following services will not receive configuration: " + srList[i], null );
+ }
+ }
+
try
{
// bind the configuration, fail if bound to another
// bundle !!
// check bundle location of configuration
- String bundleLocation = sr[0].getBundle().getLocation();
+ String bundleLocation = sr.getBundle().getLocation();
if ( config.getBundleLocation() == null )
{
// bind to the location of the service if
@@ -1215,8 +1234,14 @@
return;
}
+ // record the delivery of the configuration
+ if ( config.getServiceReference() == null )
+ {
+ config.setServiceReference( sr );
+ }
+
// prepare the configuration for the service (call plugins)
- Dictionary dictionary = callPlugins( config.getPid(), sr[0], config );
+ Dictionary dictionary = callPlugins( config.getPid(), sr, config );
// update the ManagedService with the properties
srv.updated( dictionary );
@@ -1224,23 +1249,24 @@
}
finally
{
- bundleContext.ungetService( sr[0] );
+ bundleContext.ungetService( sr );
}
}
}
else
{
- ServiceReference[] sr = bundleContext.getServiceReferences( ManagedServiceFactory.class.getName(),
- "(" + Constants.SERVICE_PID + "=" + config.getFactoryPid() + ")" );
- if ( sr != null && sr.length > 0 )
+ ServiceReference[] srList = bundleContext.getServiceReferences( ManagedServiceFactory.class
+ .getName(), "(" + Constants.SERVICE_PID + "=" + config.getFactoryPid() + ")" );
+ if ( srList != null && srList.length > 0 )
{
- ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr[0] );
+ final ServiceReference sr = srList[0];
+ final ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr );
try
{
// bind the configuration, fail if bound to another
// bundle !!
// check bundle location of configuration
- String bundleLocation = sr[0].getBundle().getLocation();
+ String bundleLocation = sr.getBundle().getLocation();
if ( config.getBundleLocation() == null )
{
// bind to the location of the service if
@@ -1259,7 +1285,7 @@
// prepare the configuration for the service (call plugins)
// call the plugins with cm.target set to the service's factory PID
// (clarification in Section 104.9.1 of Compendium 4.2)
- Dictionary dictionary = callPlugins( config.getFactoryPid(), sr[0], config );
+ Dictionary dictionary = callPlugins( config.getFactoryPid(), sr, config );
// update the ManagedServiceFactory with the properties
// only, if there is non-null configuration data
@@ -1271,7 +1297,7 @@
}
finally
{
- bundleContext.ungetService( sr[0] );
+ bundleContext.ungetService( sr );
}
}
}
@@ -1331,11 +1357,12 @@
{
if ( factoryPid == null )
{
- ServiceReference[] sr = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
+ ServiceReference[] srList = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
+ Constants.SERVICE_PID + "=" + pid + ")" );
- if ( sr != null && sr.length > 0 )
+ if ( srList != null && srList.length > 0 )
{
- ManagedService srv = ( ManagedService ) bundleContext.getService( sr[0] );
+ final ServiceReference sr = srList[0];
+ final ManagedService srv = ( ManagedService ) bundleContext.getService( sr );
try
{
srv.updated( null );
@@ -1343,7 +1370,7 @@
}
finally
{
- bundleContext.ungetService( sr[0] );
+ bundleContext.ungetService( sr );
}
}
}
@@ -1354,11 +1381,12 @@
factory.removePID( pid );
factory.store();
- ServiceReference[] sr = bundleContext.getServiceReferences( ManagedServiceFactory.class.getName(),
- "(" + Constants.SERVICE_PID + "=" + factoryPid + ")" );
- if ( sr != null && sr.length > 0 )
+ ServiceReference[] srList = bundleContext.getServiceReferences( ManagedServiceFactory.class
+ .getName(), "(" + Constants.SERVICE_PID + "=" + factoryPid + ")" );
+ if ( srList != null && srList.length > 0 )
{
- ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr[0] );
+ final ServiceReference sr = srList[0];
+ final ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr );
try
{
srv.deleted( pid );
@@ -1366,7 +1394,7 @@
}
finally
{
- bundleContext.ungetService( sr[0] );
+ bundleContext.ungetService( sr );
}
}
}
diff --git a/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationTest.java b/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationTest.java
index 5697ecf..cfadf94 100644
--- a/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationTest.java
+++ b/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationTest.java
@@ -129,6 +129,10 @@
// give cm time for distribution
delay();
+ // assert activater has configuration
+ TestCase.assertNotNull( "Expect Properties after Service Registration", tester.props );
+ TestCase.assertEquals( "Expect a single update call", 1, tester.numUpdatedCalls );
+
// ensure a freshly retrieved object also has the location
final Configuration beforeStop = getConfiguration( pid );
TestCase.assertEquals( beforeStop.getBundleLocation(), bundle.getLocation() );
@@ -160,17 +164,100 @@
// ensure a freshly retrieved object also does not have the location
final Configuration atEnd = getConfiguration( pid );
TestCase.assertNull( atEnd.getBundleLocation() );
+
+ // remove the configuration for good
+ deleteConfig( pid );
}
- private Bundle installBundle( final String pid ) throws BundleException {
+ @Test
+ public void test_start_bundle_configure_stop_start_bundle() throws IOException, BundleException
+ {
+ String pid = "test_start_bundle_configure_stop_start_bundle";
+
+ // start the bundle and assert this
+ bundle = installBundle( pid );
+ bundle.start();
+ final TestActivator tester = TestActivator.INSTANCE;
+ TestCase.assertNotNull( "Activator not started !!", tester );
+
+ // give cm time for distribution
+ delay();
+
+ // assert activater has no configuration
+ TestCase.assertNull( "Expect no Properties after Service Registration", tester.props );
+ TestCase.assertEquals( "Expect no update call", 1, tester.numUpdatedCalls );
+
+ // configure after ManagedServiceRegistration --> configure via update
+ configure( pid );
+ delay();
+
+ // assert activater has configuration
+ TestCase.assertNotNull( "Expect Properties after Service Registration", tester.props );
+ TestCase.assertEquals( "Expect a single update call", 2, tester.numUpdatedCalls );
+
+ // stop the bundle now
+ bundle.stop();
+
+ // assert INSTANCE is null
+ TestCase.assertNull( TestActivator.INSTANCE );
+
+ delay();
+
+ // start the bundle again (and check)
+ bundle.start();
+ final TestActivator tester2 = TestActivator.INSTANCE;
+ TestCase.assertNotNull( "Activator not started the second time!!", tester2 );
+ TestCase.assertNotSame( "Instances must not be the same", tester, tester2 );
+
+ // give cm time for distribution
+ delay();
+
+ // assert activater has configuration
+ TestCase.assertNotNull( "Expect Properties after Service Registration", tester2.props );
+ TestCase.assertEquals( "Expect a second update call", 1, tester2.numUpdatedCalls );
+
+ // cleanup
+ bundle.uninstall();
+ bundle = null;
+
+ // remove the configuration for good
+ deleteConfig( pid );
+ }
+
+
+ /*
+ @Test
+ public void test_() throws BundleException
+ {
+ final int count = 2;
+ for (int i=0; i < count; i++) {
+ final Bundle bundle = installBundle( "dummy", FailureActivator.class );
+ bundle.start();
+ delay();
+ bundle.uninstall();
+ delay();
+ }
+ }
+ */
+
+
+ private Bundle installBundle( final String pid ) throws BundleException
+ {
+ return installBundle( pid, TestActivator.class );
+ }
+
+
+ private Bundle installBundle( final String pid, final Class<?> activatorClass )
+ throws BundleException
+ {
final InputStream bundleStream = new MyTinyBundle()
.prepare(
withBnd()
.set( Constants.BUNDLE_SYMBOLICNAME, "simpleconfiguration" )
.set( Constants.BUNDLE_VERSION, "0.0.11" )
.set( Constants.IMPORT_PACKAGE, "org.apache.felix.cm.integration.helper" )
- .set( Constants.BUNDLE_ACTIVATOR, "org.apache.felix.cm.integration.helper.TestActivator" )
+ .set( Constants.BUNDLE_ACTIVATOR, activatorClass.getName() )
.set( TestActivator.HEADER_PID, pid )
)
.build( TinyBundles.asStream() );