FELIX-3360 Fix dynamic configuration binding

- getConfiguration(pid) binds dynamically
- createConfiguration(pid) binds dynamically
- add integration tests to validate removal of
  dynamic bindings
- make sure dynamic binding is cleared if
  static binding is set

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1513740 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
index bf3df93..67efa2e 100644
--- a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
@@ -76,8 +76,9 @@
         configurationManager.log( LogService.LOG_DEBUG, "createFactoryConfiguration(factoryPid={0})", new Object[]
             { factoryPid } );
 
-        ConfigurationImpl config = configurationManager.createFactoryConfiguration( factoryPid, this.getBundle()
-            .getLocation() );
+        // FELIX-3360: new factory configuration with implicit binding is dynamic
+        ConfigurationImpl config = configurationManager.createFactoryConfiguration( factoryPid, null );
+        config.setDynamicBundleLocation( this.getBundle().getLocation(), false );
         return this.wrap( config );
     }
 
@@ -114,7 +115,10 @@
         ConfigurationImpl config = configurationManager.getConfiguration( pid );
         if ( config == null )
         {
-            config = configurationManager.createConfiguration( pid, getBundle().getLocation() );
+            config = configurationManager.createConfiguration( pid, null );
+
+            // FELIX-3360: configuration creation with implicit binding is dynamic
+            config.setDynamicBundleLocation( getBundle().getLocation(), false );
         }
         else
         {
@@ -125,7 +129,8 @@
                         { config.getPid(), config.isNew() ? Boolean.TRUE : Boolean.FALSE,
                             this.getBundle().getLocation() } );
 
-                config.setStaticBundleLocation( this.getBundle().getLocation() );
+                // FELIX-3360: first implicit binding is dynamic
+                config.setDynamicBundleLocation( getBundle().getLocation(), true );
             }
             else
             {
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 51c3074..9ecde30 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
@@ -258,14 +258,10 @@
         this.staticBundleLocation = bundleLocation;
         storeSilently();
 
-        // check whether the dynamic bundle location is different from the
-        // static now. If so, the dynamic bundle location has to be
-        // removed.
-        if ( bundleLocation != null && getDynamicBundleLocation() != null
-            && !bundleLocation.equals( getDynamicBundleLocation() ) )
-        {
-            setDynamicBundleLocation( null, false );
-        }
+        // FELIX-3360: Always clear dynamic binding if a new static
+        // location is set. The static location is the relevant binding
+        // for a configuration unless it is not explicitly set.
+        setDynamicBundleLocation( null, false );
 
         // CM 1.4
         this.getConfigurationManager().locationChanged( this, oldBundleLocation );
@@ -295,7 +291,7 @@
      * the case of this configuration to be dynamically bound a
      * <code>CM_LOCATION_CHANGED</code> event is dispatched.
      */
-    boolean tryBindLocation( final String bundleLocation )
+    void tryBindLocation( final String bundleLocation )
     {
         if ( this.getBundleLocation() == null )
         {
@@ -303,8 +299,6 @@
                 { getPidString(), bundleLocation } );
             setDynamicBundleLocation( bundleLocation, true );
         }
-
-        return true;
     }
 
 
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 a653d4b..eb39844 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
@@ -32,8 +32,10 @@
 import org.ops4j.pax.exam.junit.JUnit4TestRunner;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.cm.ConfigurationEvent;
 import org.osgi.service.cm.ConfigurationListener;
 
@@ -1003,6 +1005,127 @@
         configListener.assertEvents( ConfigurationEvent.CM_LOCATION_CHANGED, 0 );
     }
 
+    /**
+     * Tests configuration dynamic binding. See FELIX-3360.
+     */
+    @SuppressWarnings({ "serial", "javadoc" })
+    @Test
+    public void test_dynamic_binding_getConfiguration_pid() throws BundleException, IOException {
+        String ignoredPid = "test_dynamic_binding_getConfiguration_pid_ignored";
+        String pid1 = "test_dynamic_binding_getConfiguration_pid_1";
+        String pid2 = "test_dynamic_binding_getConfiguration_pid_2";
+
+        // ensure configuration is unbound
+        configure( pid1 );
+        delay();
+        configListener.assertEvents( ConfigurationEvent.CM_UPDATED, 1 );
+
+        bundle = installBundle( ignoredPid );
+        bundle.start();
+        delay();
+
+        // ensure config1 unbound
+        Configuration config1 = getConfiguration( pid1 );
+        TestCase.assertNull( config1.getBundleLocation() );
+
+        ServiceReference<ConfigurationAdmin> sr = bundle.getBundleContext().getServiceReference( ConfigurationAdmin.class );
+        ConfigurationAdmin bundleCa = bundle.getBundleContext().getService( sr );
+
+        // ensure dynamic binding
+        Configuration bundleConfig1 = bundleCa.getConfiguration( pid1 );
+        TestCase.assertEquals( bundle.getLocation(), bundleConfig1.getBundleLocation() );
+        delay();
+        configListener.assertEvents( ConfigurationEvent.CM_LOCATION_CHANGED, 1 );
+
+        // create config2; ensure dynamic binding
+        Configuration bundleConfig2 = bundleCa.getConfiguration( pid2 );
+        TestCase.assertNull(bundleConfig2.getProperties());
+        TestCase.assertEquals( bundle.getLocation(), bundleConfig2.getBundleLocation() );
+        bundleConfig2.update( new Hashtable<String, String>()
+        {
+            {
+                put( "key", "value" );
+            }
+        } );
+
+        // uninstall the bundle, 2 dynamic locations changed
+        bundle.uninstall();
+        bundle = null;
+        delay();
+        configListener.assertEvents( ConfigurationEvent.CM_LOCATION_CHANGED, 2 );
+
+        bundleConfig1 = getConfiguration( pid1 );
+        TestCase.assertNull( bundleConfig1.getBundleLocation() );
+
+        bundleConfig2 = getConfiguration( pid2 );
+        TestCase.assertNull(bundleConfig2.getBundleLocation());
+
+        bundleConfig1.delete();
+        bundleConfig2.delete();
+    }
+
+    /**
+     * Tests factory configuration dynamic binding. See FELIX-3360.
+     */
+    @SuppressWarnings({ "javadoc", "serial" })
+    @Test
+    public void test_dynamic_binding_createFactoryConfiguration_pid() throws BundleException, IOException {
+        String ignoredPid = "test_dynamic_binding_createFactoryConfiguration_pid_ignored";
+        String pid1 = null;
+        String pid2 = null;
+        String factoryPid1 = "test_dynamic_binding_createFactoryConfiguration_pid_1";
+        String factoryPid2 = "test_dynamic_binding_createFactoryConfiguration_pid_2";
+
+        // ensure configuration is unbound
+        pid1 = createFactoryConfiguration( factoryPid1 ).getPid();
+        delay();
+        configListener.assertEvents( ConfigurationEvent.CM_UPDATED, 1 );
+
+        bundle = installBundle( ignoredPid );
+        bundle.start();
+        delay();
+
+        // ensure config1 unbound
+        Configuration config1 = getConfiguration( pid1 );
+        TestCase.assertNull( config1.getBundleLocation() );
+
+        ServiceReference<ConfigurationAdmin> sr = bundle.getBundleContext().getServiceReference( ConfigurationAdmin.class );
+        ConfigurationAdmin bundleCa = bundle.getBundleContext().getService( sr );
+
+        // ensure dynamic binding
+        Configuration bundleConfig1 = bundleCa.getConfiguration( pid1 );
+        TestCase.assertEquals( bundle.getLocation(), bundleConfig1.getBundleLocation() );
+        delay();
+        configListener.assertEvents( ConfigurationEvent.CM_LOCATION_CHANGED, 1 );
+
+        // create config2; ensure dynamic binding
+        Configuration bundleConfig2 = bundleCa.createFactoryConfiguration( factoryPid2 );
+        pid2 = bundleConfig2.getPid();
+        TestCase.assertNull(bundleConfig2.getProperties());
+        TestCase.assertEquals( bundle.getLocation(), bundleConfig2.getBundleLocation() );
+        bundleConfig2.update( new Hashtable<String, String>()
+        {
+            {
+                put( "key", "value" );
+            }
+        } );
+
+        // uninstall the bundle, 2 dynamic locations changed
+        bundle.uninstall();
+        bundle = null;
+        delay();
+        configListener.assertEvents( ConfigurationEvent.CM_LOCATION_CHANGED, 2 );
+
+        bundleConfig1 = getConfiguration( pid1 );
+        TestCase.assertNull( bundleConfig1.getBundleLocation() );
+
+        bundleConfig2 = getConfiguration( pid2 );
+        TestCase.assertNull(bundleConfig2.getBundleLocation());
+
+        bundleConfig1.delete();
+        bundleConfig2.delete();
+    }
+
     private static class ConfigListener implements ConfigurationListener {
 
         private int[] events = new int[3];
@@ -1018,5 +1141,14 @@
             TestCase.assertEquals( "Events of type " + type, numEvents, events[type - 1] );
             events[type - 1] = 0;
         }
+
+
+        void reset()
+        {
+            for ( int i = 0; i < events.length; i++ )
+            {
+                events[i] = 0;
+            }
+        }
     }
 }