FELIX-1488 support for another use case:
  1. Create configuration pid1 with null location and set properties
  2. Install bundle locationA with ManagedServiceA pid1
  3. Install bundle locationB with ManagedServiceB pid1
  4. Uninstall bundle locationA
  ==> configuration to be assigned to locationB/pid1 service

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@805776 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationBase.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationBase.java
index d5e72d6..b5192cf 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationBase.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationBase.java
@@ -23,7 +23,6 @@
 import java.util.Dictionary;
 import org.apache.felix.cm.PersistenceManager;
 import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.log.LogService;
 
 
@@ -65,7 +64,7 @@
 
 
     protected ConfigurationBase( final ConfigurationManager configurationManager,
-        final PersistenceManager persistenceManager, final String baseId, final Dictionary props )
+        final PersistenceManager persistenceManager, final String baseId, final String bundleLocation )
     {
         if ( configurationManager == null )
         {
@@ -82,11 +81,7 @@
         this.baseId = baseId;
 
         // set bundle location from persistence and/or check for dynamic binding
-        if ( props != null )
-        {
-            this.staticBundleLocation = ( String ) props.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
-        }
-
+        this.staticBundleLocation = bundleLocation;
         this.dynamicBundleLocation = configurationManager.getDynamicBundleLocation( baseId );
     }
 
@@ -154,25 +149,24 @@
 
     void setStaticBundleLocation( final String bundleLocation )
     {
-        // FELIX-1488: If a configuration is bound to a location and a new
-        // location is statically set, the old binding must be removed
-        // by removing the configuration from the targets and the new binding
-        // must be setup by updating the configuration for new targets
-        boolean replace = ( this instanceof ConfigurationImpl ) && ( bundleLocation != null );
-        if ( replace && getDynamicBundleLocation() != null && !bundleLocation.equals( getDynamicBundleLocation() ) )
-        {
-            // remove configuration from current managed service [factory]
-            getConfigurationManager().deleted( ( ConfigurationImpl ) this, false );
-        }
-
         // 104.15.2.8 The bundle location will be set persistently
         this.staticBundleLocation = bundleLocation;
         storeSilently();
 
-        // check whether we have to assign the configuration to new targets
-        if ( replace )
+        // FELIX-1488: If a configuration is bound to a location and a new
+        // location is statically set, the old binding must be removed
+        // by removing the configuration from the targets and the new binding
+        // must be setup by updating the configuration for new targets
+        if ( ( this instanceof ConfigurationImpl ) && ( bundleLocation != null ) )
         {
-            getConfigurationManager().updated( ( ConfigurationImpl ) this, false );
+            // remove configuration from current managed service [factory]
+            if ( getDynamicBundleLocation() != null && !bundleLocation.equals( getDynamicBundleLocation() ) )
+            {
+                getConfigurationManager().revokeConfiguration( ( ConfigurationImpl ) this );
+            }
+
+            // check whether we have to assign the configuration to new targets
+            getConfigurationManager().reassignConfiguration( ( ConfigurationImpl ) this );
         }
     }
 
@@ -181,6 +175,14 @@
     {
         this.dynamicBundleLocation = bundleLocation;
         this.configurationManager.setDynamicBundleLocation( this.getBaseId(), bundleLocation );
+
+        // FELIX-1488: If a dynamically bound configuration is unbound and not
+        // statically bound, it may be rebound to another bundle asking for it
+        // (unless the dynamic unbind happens due to configuration deletion)
+        if ( bundleLocation == null && getStaticBundleLocation() == null && ( this instanceof ConfigurationImpl ) )
+        {
+            getConfigurationManager().reassignConfiguration( ( ConfigurationImpl ) this );
+        }
     }
 
 
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
index e3288ed..b64a910 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
@@ -84,7 +84,8 @@
     ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager,
         Dictionary properties )
     {
-        super( configurationManager, persistenceManager, ( String ) properties.remove( Constants.SERVICE_PID ), properties );
+        super( configurationManager, persistenceManager, ( String ) properties.remove( Constants.SERVICE_PID ),
+            ( String ) properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) );
 
         this.factoryPID = ( String ) properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
         this.isDeleted = false;
@@ -97,15 +98,12 @@
     ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String pid,
         String factoryPid, String bundleLocation ) throws IOException
     {
-        super( configurationManager, persistenceManager, pid, null );
+        super( configurationManager, persistenceManager, pid, bundleLocation );
 
         this.factoryPID = factoryPid;
         this.isDeleted = false;
         this.properties = null;
 
-        // static bundle binding here
-        setStaticBundleLocation( bundleLocation );
-
         // this is a new configuration object, store immediately unless
         // the new configuration object is created from a factory, in which
         // case the configuration is only stored when first updated
@@ -123,7 +121,8 @@
     {
         this.isDeleted = true;
         getPersistenceManager().delete( this.getPid() );
-        getConfigurationManager().deleted( this, true );
+        getConfigurationManager().setDynamicBundleLocation( this.getPid(), null );
+        getConfigurationManager().deleted( this );
     }
 
 
@@ -190,7 +189,7 @@
 
             configureFromPersistence( properties );
 
-            getConfigurationManager().updated( this, true );
+            getConfigurationManager().updated( this );
         }
     }
 
@@ -241,7 +240,7 @@
             // finally assign the configuration for use
             configure( newProperties );
 
-            getConfigurationManager().updated( this, true );
+            getConfigurationManager().updated( this );
         }
     }
 
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 06b270f..82590ed 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
@@ -558,17 +558,33 @@
     }
 
 
-    void deleted( ConfigurationImpl config, boolean fireEvent )
+    void deleted( ConfigurationImpl config )
     {
         // remove the configuration from the cache
         removeConfiguration( config );
-        updateThread.schedule( new DeleteConfiguration( config, fireEvent ) );
+        updateThread.schedule( new DeleteConfiguration( config, true ) );
     }
 
 
-    void updated( ConfigurationImpl config, boolean fireEvent )
+    void updated( ConfigurationImpl config )
     {
-        updateThread.schedule( new UpdateConfiguration( config, fireEvent ) );
+        updateThread.schedule( new UpdateConfiguration( config, true ) );
+    }
+
+
+    void revokeConfiguration( ConfigurationImpl config )
+    {
+        updateThread.schedule( new DeleteConfiguration( config, false ) );
+
+        // immediately unbind the configuration
+        config.setDynamicBundleLocation( null );
+        config.setServiceReference( null );
+    }
+
+
+    void reassignConfiguration( ConfigurationImpl config )
+    {
+        updateThread.schedule( new UpdateConfiguration( config, false ) );
     }
 
 
@@ -1508,10 +1524,6 @@
             this.factoryPid = config.getFactoryPid();
             this.configLocation = config.getBundleLocation();
             this.fireEvent = fireEvent;
-
-            // immediately unbind the configuration
-            config.setDynamicBundleLocation( null );
-            config.setServiceReference( null );
         }
 
 
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java b/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
index 1c59d9e..48589f1 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
@@ -72,8 +72,8 @@
 
     Factory( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String factoryPid, Dictionary props )
     {
-        super(configurationManager, persistenceManager, factoryPid, props);
-
+        super( configurationManager, persistenceManager, factoryPid, ( String ) props
+            .get( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) );
 
         // set pids
         String[] pidList = ( String[] ) props.get( FACTORY_PID_LIST );
diff --git a/configadmin/src/test/java/org/apache/felix/cm/impl/MockConfigurationManager.java b/configadmin/src/test/java/org/apache/felix/cm/impl/MockConfigurationManager.java
index 527e348..39277b4 100644
--- a/configadmin/src/test/java/org/apache/felix/cm/impl/MockConfigurationManager.java
+++ b/configadmin/src/test/java/org/apache/felix/cm/impl/MockConfigurationManager.java
@@ -22,13 +22,25 @@
 public class MockConfigurationManager extends ConfigurationManager
 {
 
-    void updated( ConfigurationImpl config, boolean fireEvent )
+    void updated( ConfigurationImpl config )
     {
         // do nothing, might register the update call
     }
 
 
-    void deleted( ConfigurationImpl config, boolean fireEvent )
+    void deleted( ConfigurationImpl config )
+    {
+        // do nothing, might register the update call
+    }
+
+
+    void revokeConfiguration( ConfigurationImpl config )
+    {
+        // do nothing, might register the update call
+    }
+
+
+    void reassignConfiguration( ConfigurationImpl config )
     {
         // do nothing, might register the update call
     }
diff --git a/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationBindingTest.java b/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationBindingTest.java
index 1177d7f..e269eff 100644
--- a/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationBindingTest.java
+++ b/configadmin/src/test/java/org/apache/felix/cm/integration/ConfigurationBindingTest.java
@@ -42,7 +42,7 @@
      @Test
     public void test_configuration_unbound_on_uninstall() throws BundleException
     {
-        String pid = "test.pid";
+        String pid = "test_configuration_unbound_on_uninstall";
         configure( pid );
 
         // ensure configuration is unbound
@@ -111,7 +111,7 @@
      @Test
     public void test_configuration_unbound_on_uninstall_with_cm_restart() throws BundleException
     {
-        final String pid = "test.pid";
+        final String pid = "test_configuration_unbound_on_uninstall_with_cm_restart";
         configure( pid );
         final Bundle cmBundle = getCmBundle();
 
@@ -620,6 +620,7 @@
         // 5. Call Configuration.setBundleLocation( "locationB" )
         config.setBundleLocation( locationB );
         delay();
+        delay();
 
         // ==> configuration is bound to locationB
         TestCase.assertEquals( locationB, config.getBundleLocation() );
@@ -668,6 +669,7 @@
         // 5. Call Configuration.setBundleLocation( "locationB" )
         config.setBundleLocation( locationB );
         delay();
+        delay();
 
         // ==> configuration is bound to locationB
         TestCase.assertEquals( locationB, config.getBundleLocation() );
@@ -693,6 +695,8 @@
         final Configuration config = createFactoryConfiguration( factoryPid, null, true );
         final String pid = config.getPid();
 
+        TestCase.assertNull( config.getBundleLocation() );
+
         // 3. register ManagedService ms1 with pid from said locationA
         final Bundle bundleA = installBundle( factoryPid, ManagedServiceFactoryTestActivator.class, locationA );
         bundleA.start();
@@ -702,6 +706,7 @@
         final ManagedServiceFactoryTestActivator testerA1 = ManagedServiceFactoryTestActivator.INSTANCE;
         TestCase.assertNotNull( testerA1.configs.get( pid ) );
         TestCase.assertEquals( 1, testerA1.numManagedServiceFactoryUpdatedCalls );
+        TestCase.assertEquals( locationA, config.getBundleLocation() );
 
         // 4. register ManagedService ms2 with pid from locationB
         final String locationB = "test:location/B/" + factoryPid;
@@ -713,10 +718,12 @@
         final ManagedServiceFactoryTestActivator2 testerB1 = ManagedServiceFactoryTestActivator2.INSTANCE;
         TestCase.assertNull( testerB1.configs.get( pid ));
         TestCase.assertEquals( 0, testerB1.numManagedServiceFactoryUpdatedCalls );
+        TestCase.assertEquals( locationA, config.getBundleLocation() );
 
         // 5. Call Configuration.setBundleLocation( "locationB" )
         config.setBundleLocation( locationB );
         delay();
+        delay();
 
         // ==> configuration is bound to locationB
         TestCase.assertEquals( locationB, config.getBundleLocation() );
@@ -730,4 +737,102 @@
         TestCase.assertNotNull( testerB1.configs.get( pid ) );
         TestCase.assertEquals( 1, testerB1.numManagedServiceFactoryUpdatedCalls );
     }
+
+
+    @Test
+    public void test_switch_dynamic_binding_after_uninstall() throws BundleException
+    {
+        // 1. create config with pid with null location
+        // 2. update config with properties
+        final String pid = "test_switch_dynamic_binding";
+        final String locationA = "test:location/A/" + pid;
+        final Configuration config = configure( pid, null, true );
+
+        TestCase.assertNull( config.getBundleLocation() );
+
+        // 3. register ManagedService ms1 with pid from locationA
+        final Bundle bundleA = installBundle( pid, ManagedServiceTestActivator.class, locationA );
+        bundleA.start();
+        delay();
+
+        // ==> configuration supplied to the service ms1
+        final ManagedServiceTestActivator testerA1 = ManagedServiceTestActivator.INSTANCE;
+        TestCase.assertNotNull( testerA1.props );
+        TestCase.assertEquals( 1, testerA1.numManagedServiceUpdatedCalls );
+
+        // ==> configuration is dynamically bound to locationA
+        TestCase.assertEquals( locationA, config.getBundleLocation() );
+
+        // 4. register ManagedService ms2 with pid from locationB
+        final String locationB = "test:location/B/" + pid;
+        final Bundle bundleB = installBundle( pid, ManagedServiceTestActivator2.class, locationB );
+        bundleB.start();
+        delay();
+
+        // ==> configuration not supplied to service ms2
+        final ManagedServiceTestActivator2 testerB1 = ManagedServiceTestActivator2.INSTANCE;
+        TestCase.assertNull( testerB1.props );
+        TestCase.assertEquals( 0, testerB1.numManagedServiceUpdatedCalls );
+
+        // 5. Uninstall bundle A
+        bundleA.uninstall();
+        delay();
+        delay();
+
+        // ==> configuration is bound to locationB
+        TestCase.assertEquals( locationB, config.getBundleLocation() );
+
+        // ==> configuration supplied to the service ms2
+        TestCase.assertNotNull( testerB1.props );
+        TestCase.assertEquals( 1, testerB1.numManagedServiceUpdatedCalls );
+    }
+
+
+    @Test
+    public void test_switch_dynamic_binding_factory_after_uninstall() throws BundleException
+    {
+        // 1. create config with pid and locationA
+        // 2. update config with properties
+        final String factoryPid = "test_switch_static_binding_factory";
+        final String locationA = "test:location/A/" + factoryPid;
+        final Configuration config = createFactoryConfiguration( factoryPid, null, true );
+        final String pid = config.getPid();
+
+        TestCase.assertNull( config.getBundleLocation() );
+
+        // 3. register ManagedService ms1 with pid from said locationA
+        final Bundle bundleA = installBundle( factoryPid, ManagedServiceFactoryTestActivator.class, locationA );
+        bundleA.start();
+        delay();
+
+        // ==> configuration supplied to the service ms1
+        final ManagedServiceFactoryTestActivator testerA1 = ManagedServiceFactoryTestActivator.INSTANCE;
+        TestCase.assertNotNull( testerA1.configs.get( pid ) );
+        TestCase.assertEquals( 1, testerA1.numManagedServiceFactoryUpdatedCalls );
+        TestCase.assertEquals( locationA, config.getBundleLocation() );
+
+        // 4. register ManagedService ms2 with pid from locationB
+        final String locationB = "test:location/B/" + factoryPid;
+        final Bundle bundleB = installBundle( factoryPid, ManagedServiceFactoryTestActivator2.class, locationB );
+        bundleB.start();
+        delay();
+
+        // ==> configuration not supplied to service ms2
+        final ManagedServiceFactoryTestActivator2 testerB1 = ManagedServiceFactoryTestActivator2.INSTANCE;
+        TestCase.assertNull( testerB1.configs.get( pid ));
+        TestCase.assertEquals( 0, testerB1.numManagedServiceFactoryUpdatedCalls );
+        TestCase.assertEquals( locationA, config.getBundleLocation() );
+
+        // 5. Uninstall bundle A
+        bundleA.uninstall();
+        delay();
+        delay();
+
+        // ==> configuration is bound to locationB
+        TestCase.assertEquals( locationB, config.getBundleLocation() );
+
+        // ==> configuration supplied to the service ms2
+        TestCase.assertNotNull( testerB1.configs.get( pid ) );
+        TestCase.assertEquals( 1, testerB1.numManagedServiceFactoryUpdatedCalls );
+    }
 }
diff --git a/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServiceFactoryPIDTest.java b/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServiceFactoryPIDTest.java
index 2d09b54..11dfc93 100644
--- a/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServiceFactoryPIDTest.java
+++ b/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServiceFactoryPIDTest.java
@@ -165,7 +165,8 @@
 
             delay();
 
-            TestCase.assertNull( config.getBundleLocation() );
+            // expect configuration reassigned
+            TestCase.assertEquals( bundle2.getLocation(), config.getBundleLocation() );
 
             // remove the configuration for good
             deleteConfig( pid );
@@ -230,7 +231,8 @@
 
                 delay();
 
-                TestCase.assertNull( config.getBundleLocation() );
+                // expect configuration reassigned
+                TestCase.assertEquals( bundle2.getLocation(), config.getBundleLocation() );
             }
             else if ( bundle2.getLocation().equals( config.getBundleLocation() ) )
             {
@@ -247,8 +249,8 @@
 
                 delay();
 
-                // really ??
-                TestCase.assertNull( config.getBundleLocation() );
+                // expect configuration reassigned
+                TestCase.assertEquals( bundle.getLocation(), config.getBundleLocation() );
             }
             else
             {
diff --git a/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServicePIDTest.java b/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServicePIDTest.java
index d69e509..f0c5058 100644
--- a/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServicePIDTest.java
+++ b/configadmin/src/test/java/org/apache/felix/cm/integration/MultiServicePIDTest.java
@@ -166,7 +166,8 @@
 
             delay();
 
-            TestCase.assertNull( config.getBundleLocation() );
+            // expect configuration reassigned
+            TestCase.assertEquals( bundle2.getLocation(), config.getBundleLocation() );
 
             // remove the configuration for good
             deleteConfig( pid );
@@ -231,7 +232,8 @@
 
                 delay();
 
-                TestCase.assertNull( config.getBundleLocation() );
+                // config has to be reassigned
+                TestCase.assertEquals( bundle2.getLocation(), config.getBundleLocation() );
             }
             else if ( bundle2.getLocation().equals( config.getBundleLocation() ) )
             {
@@ -248,8 +250,8 @@
 
                 delay();
 
-                // really ??
-                TestCase.assertNull( config.getBundleLocation() );
+                // config has to be reassigned
+                TestCase.assertEquals( bundle.getLocation(), config.getBundleLocation() );
             }
             else
             {