FELIX-1488 Dynamic configuration bindings have to also be
persisted for this use case:
  1. start managed service s1 of bundle b1
  2. configuration is prodived and bound to b1
  3. stop bundle b1
  4. restart configuration admin service
  ==> configuration must still be bound to b1

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@804891 13f79535-47bb-0310-9956-ffa450edef68
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 d67b549..b8d6f4b 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
@@ -145,8 +145,18 @@
 
         this.pid = ( String ) properties.remove( Constants.SERVICE_PID );
         this.factoryPID = ( String ) properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
+
+        // set bundle location from persistence and/or check for dynamic binding
         this.bundleLocation = ( String ) properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
-        this.staticallyBound = bundleLocation != null;
+        if ( bundleLocation != null )
+        {
+            this.staticallyBound = true;
+        }
+        else
+        {
+            this.staticallyBound = false;
+            this.bundleLocation = configurationManager.getDynamicBundleLocation( this.pid );
+        }
 
         // set the properties internally
         configureFromPersistence( properties );
@@ -289,7 +299,7 @@
     /* (non-Javadoc)
      * @see org.osgi.service.cm.Configuration#setBundleLocation(java.lang.String)
      */
-    public void setBundleLocation( String bundleLocation, boolean staticBinding )
+    public void setBundleLocation( final String bundleLocation, final boolean staticBinding )
     {
         if ( !isDeleted() )
         {
@@ -311,8 +321,11 @@
             }
             else if ( !staticallyBound )
             {
-                // dynamic binding, no need to store
+                // dynamic binding
                 this.bundleLocation = bundleLocation;
+
+                // keep the dynamic binding
+                this.configurationManager.setDynamicBundleLocation( this.getPid(), bundleLocation );
             }
         }
     }
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 0644780..0315950 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
@@ -141,6 +141,19 @@
     // have this always set to prevent NPE on bundle shutdown
     private final Map configurations = new HashMap();
 
+    /**
+     * The map of dynamic configuration bindings. This maps the
+     * PID of the dynamically bound configuration or factory to its bundle
+     * location.
+     * <p>
+     * On bundle startup this map is loaded from persistence and validated
+     * against the locations of installed bundles: Entries pointing to bundle
+     * locations not currently installed are removed.
+     * <p>
+     * The map is written to persistence on each change.
+     */
+    private DynamicBindings dynamicBindings;
+
     // the maximum log level when no LogService is available
     private int logLevel = CM_LOG_LEVEL_DEFAULT;
 
@@ -193,6 +206,13 @@
             props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
             props.put( Constants.SERVICE_RANKING, new Integer( Integer.MIN_VALUE ) );
             bundleContext.registerService( PersistenceManager.class.getName(), fpm, props );
+
+            // setup dynamic configuration bindings
+            dynamicBindings = new DynamicBindings( bundleContext, fpm );
+        }
+        catch ( IOException ioe )
+        {
+            log( LogService.LOG_ERROR, "Failure setting up dynamic configuration bindings", ioe );
         }
         catch ( IllegalArgumentException iae )
         {
@@ -363,8 +383,37 @@
     }
 
 
+    // ---------- ConfigurationAdminImpl support
 
-    // ---------- ConfigurationAdminImpl support -------------------------------
+    void setDynamicBundleLocation( final String pid, final String location )
+    {
+        if ( dynamicBindings != null )
+        {
+            try
+            {
+                dynamicBindings.putLocation( pid, location );
+            }
+            catch ( IOException ioe )
+            {
+                log( LogService.LOG_ERROR, "Failed storing dynamic configuration binding for " + pid + " to "
+                    + location, ioe );
+            }
+        }
+    }
+
+
+    String getDynamicBundleLocation( final String pid )
+    {
+        if ( dynamicBindings != null )
+        {
+            return dynamicBindings.getLocation( pid );
+        }
+
+        return null;
+    }
+
+
+    // ---------- ConfigurationAdminImpl support
 
     /*
      * (non-Javadoc)
@@ -691,7 +740,7 @@
         {
             if ( Factory.exists( pmList[i], factoryPid ) )
             {
-                factory = Factory.load( pmList[i], factoryPid );
+                factory = Factory.load( this, pmList[i], factoryPid );
                 cacheFactory( factory );
                 return factory;
             }
@@ -704,7 +753,7 @@
 
     Factory createFactory( String factoryPid )
     {
-        Factory factory = new Factory( getPersistenceManagers()[0], factoryPid );
+        Factory factory = new Factory( this, getPersistenceManagers()[0], factoryPid );
         cacheFactory( factory );
         return factory;
     }
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/DynamicBindings.java b/configadmin/src/main/java/org/apache/felix/cm/impl/DynamicBindings.java
new file mode 100644
index 0000000..e242e9d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/DynamicBindings.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+
+class DynamicBindings
+{
+
+    static final String BINDINGS_FILE_NAME = "org_apache_felix_cm_impl_DynamicBindings";
+
+    private final PersistenceManager persistenceManager;
+
+    private final Dictionary bindings;
+
+
+    DynamicBindings( BundleContext bundleContext, PersistenceManager persistenceManager ) throws IOException
+    {
+        this.persistenceManager = persistenceManager;
+
+        if ( persistenceManager.exists( BINDINGS_FILE_NAME ) )
+        {
+            this.bindings = persistenceManager.load( BINDINGS_FILE_NAME );
+
+            // get locations of installed bundles to validate the bindings
+            final HashSet locations = new HashSet();
+            final Bundle[] bundles = bundleContext.getBundles();
+            for ( int i = 0; i < bundles.length; i++ )
+            {
+                locations.add( bundles[i].getLocation() );
+            }
+
+            // collect pids whose location is not installed any more
+            ArrayList removedKeys = new ArrayList();
+            for ( Enumeration ke = bindings.keys(); ke.hasMoreElements(); )
+            {
+                final String pid = ( String ) ke.nextElement();
+                final String location = ( String ) bindings.get( pid );
+                if ( !locations.contains( location ) )
+                {
+                    removedKeys.add( pid );
+                }
+            }
+
+            // if some bindings had to be removed, store the mapping again
+            if ( removedKeys.size() > 0 )
+            {
+                // remove invalid mappings
+                for ( Iterator rki = removedKeys.iterator(); rki.hasNext(); )
+                {
+                    bindings.remove( rki.next() );
+                }
+
+                // store the modified map
+                persistenceManager.store( BINDINGS_FILE_NAME, bindings );
+            }
+        }
+        else
+        {
+            this.bindings = new Hashtable();
+        }
+
+    }
+
+
+    String getLocation( final String pid )
+    {
+        synchronized ( this )
+        {
+            return ( String ) this.bindings.get( pid );
+        }
+    }
+
+
+    void putLocation( final String pid, final String location ) throws IOException
+    {
+        synchronized ( this )
+        {
+            if ( location == null )
+            {
+                this.bindings.remove( pid );
+            }
+            else
+            {
+                this.bindings.put( pid, location );
+            }
+
+            this.persistenceManager.store( BINDINGS_FILE_NAME, bindings );
+        }
+    }
+}
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 366090d..9bff795 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
@@ -27,6 +27,7 @@
 
 import org.apache.felix.cm.PersistenceManager;
 import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogService;
 
 
 /**
@@ -40,20 +41,26 @@
 
     public static final String FACTORY_PID_LIST = "factory.pidList";
 
+    /**
+     * The {@link ConfigurationManager configuration manager} instance which
+     * caused this configuration object to be created.
+     */
+    private final ConfigurationManager configurationManager;
+
     // the persistence manager storing this factory mapping
-    private PersistenceManager persistenceManager;
+    private final PersistenceManager persistenceManager;
 
     // the factory PID of this factory
-    private String factoryPid;
+    private final String factoryPid;
 
     // the bundle location to which factory PID mapping is bound
-    private String bundleLocation;
+    private volatile String bundleLocation;
 
     // whether the factory is statically bound to a bundle or not
-    private boolean staticallyBound;
+    private volatile boolean staticallyBound;
 
     // the set of configuration PIDs belonging to this factory
-    private Set pids;
+    private final Set pids;
 
 
     static boolean exists( PersistenceManager persistenceManager, String factoryPid )
@@ -62,23 +69,11 @@
     }
 
 
-    static Factory load( PersistenceManager persistenceManager, String factoryPid ) throws IOException
+    static Factory load( ConfigurationManager configurationManager, PersistenceManager persistenceManager,
+        String factoryPid ) throws IOException
     {
         Dictionary dict = persistenceManager.load( factoryPidToIdentifier( factoryPid ) );
-        return new Factory( persistenceManager, factoryPid, dict );
-    }
-
-
-    static Factory getFactory( PersistenceManager persistenceManager, Dictionary props )
-    {
-        // ignore non-Configuration dictionaries
-        String factoryPid = ( String ) props.get( Factory.FACTORY_PID );
-        if ( factoryPid == null )
-        {
-            return null;
-        }
-
-        return new Factory( persistenceManager, factoryPid, props );
+        return new Factory( configurationManager, persistenceManager, factoryPid, dict );
     }
 
 
@@ -88,8 +83,9 @@
     }
 
 
-    Factory( PersistenceManager persistenceManager, String factoryPid )
+    Factory( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String factoryPid )
     {
+        this.configurationManager = configurationManager;
         this.persistenceManager = persistenceManager;
         this.factoryPid = factoryPid;
         this.pids = new HashSet();
@@ -98,13 +94,21 @@
     }
 
 
-    Factory( PersistenceManager persistenceManager, String factoryPid, Dictionary props )
+    Factory( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String factoryPid, Dictionary props )
     {
-        this( persistenceManager, factoryPid );
+        this( configurationManager, persistenceManager, factoryPid );
 
-        // set bundle location
+        // set bundle location from persistence and/or check for dynamic binding
         this.bundleLocation = ( String ) props.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
-        this.staticallyBound = this.bundleLocation != null;
+        if ( bundleLocation != null )
+        {
+            this.staticallyBound = true;
+        }
+        else
+        {
+            this.staticallyBound = false;
+            this.bundleLocation = configurationManager.getDynamicBundleLocation( getFactoryPid() );
+        }
 
         // set pids
         String[] pidList = ( String[] ) props.get( FACTORY_PID_LIST );
@@ -148,7 +152,11 @@
         }
         else if ( !this.staticallyBound )
         {
+            // dynamic binding
             this.bundleLocation = bundleLocation;
+
+            // keep the dynamic binding
+            this.configurationManager.setDynamicBundleLocation( this.getFactoryPid(), bundleLocation );
         }
     }
 
@@ -206,9 +214,7 @@
         }
         catch ( IOException ioe )
         {
-            // should actually log this problem
-            // configurationManager.log( LogService.LOG_ERROR, "Persisting new
-            // bundle location failed", ioe );
+            configurationManager.log( LogService.LOG_ERROR, "Persisting new bundle location failed", ioe );
         }
     }
 }
diff --git a/configadmin/src/test/java/org/apache/felix/cm/MockBundle.java b/configadmin/src/test/java/org/apache/felix/cm/MockBundle.java
new file mode 100644
index 0000000..d1ef4e7
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/MockBundle.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.cm;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+
+
+public class MockBundle implements Bundle
+{
+
+    private final BundleContext context;
+    private final String location;
+
+
+    public MockBundle( BundleContext context, String location )
+    {
+        this.context = context;
+        this.location = location;
+    }
+
+
+    public Enumeration findEntries( String arg0, String arg1, boolean arg2 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public BundleContext getBundleContext()
+    {
+        return context;
+    }
+
+
+    public long getBundleId()
+    {
+        return 0;
+    }
+
+
+    public URL getEntry( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public Enumeration getEntryPaths( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public Dictionary getHeaders()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public Dictionary getHeaders( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public long getLastModified()
+    {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+
+    public String getLocation()
+    {
+        return location;
+    }
+
+
+    public ServiceReference[] getRegisteredServices()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public URL getResource( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public Enumeration getResources( String arg0 ) throws IOException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public ServiceReference[] getServicesInUse()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public int getState()
+    {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+
+    public String getSymbolicName()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public boolean hasPermission( Object arg0 )
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+
+    public Class loadClass( String arg0 ) throws ClassNotFoundException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    public void start() throws BundleException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    public void stop() throws BundleException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    public void uninstall() throws BundleException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    public void update() throws BundleException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    public void update( InputStream arg0 ) throws BundleException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java b/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java
index c0aaae2..8cb6353 100644
--- a/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java
+++ b/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -155,7 +155,7 @@
      */
     public Bundle[] getBundles()
     {
-        return null;
+        return new Bundle[0];
     }
 
 
diff --git a/configadmin/src/test/java/org/apache/felix/cm/impl/DynamicBindingsTest.java b/configadmin/src/test/java/org/apache/felix/cm/impl/DynamicBindingsTest.java
new file mode 100644
index 0000000..b3a3244
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/impl/DynamicBindingsTest.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.cm.impl;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Dictionary;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.cm.MockBundle;
+import org.apache.felix.cm.MockBundleContext;
+import org.apache.felix.cm.file.FilePersistenceManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+
+public class DynamicBindingsTest extends TestCase
+{
+
+    private File configLocation;
+
+    private File bindingsFile;
+
+    private FilePersistenceManager persistenceManager;
+
+    private static final String PID1 = "test.pid.1";
+
+    private static final String PID2 = "test.pid.2";
+
+    private static final String LOCATION1 = "test://location.1";
+
+    private static final String LOCATION2 = "test://location.2";
+
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+
+        configLocation = new File( "target/config." + System.currentTimeMillis() );
+        persistenceManager = new FilePersistenceManager( configLocation.getAbsolutePath() );
+
+        bindingsFile = new File( configLocation, DynamicBindings.BINDINGS_FILE_NAME + ".config" );
+    }
+
+
+    protected void tearDown() throws Exception
+    {
+        bindingsFile.delete();
+        configLocation.delete();
+
+        super.tearDown();
+    }
+
+
+    public void test_no_bindings() throws IOException
+    {
+
+        // ensure there is no file
+        bindingsFile.delete();
+
+        final BundleContext ctx = new MockBundleContext();
+        final DynamicBindings dm = new DynamicBindings( ctx, persistenceManager );
+        final Dictionary bindings = getBindings( dm );
+
+        assertNotNull( bindings );
+        assertTrue( bindings.isEmpty() );
+    }
+
+
+    public void test_store_bindings() throws IOException
+    {
+        // ensure there is no file
+        bindingsFile.delete();
+
+        final BundleContext ctx = new MockBundleContext();
+        final DynamicBindings dm = new DynamicBindings( ctx, persistenceManager );
+
+        dm.putLocation( PID1, LOCATION1 );
+        assertEquals( LOCATION1, dm.getLocation( PID1 ) );
+
+        assertTrue( bindingsFile.exists() );
+
+        final Dictionary bindings = persistenceManager.load( DynamicBindings.BINDINGS_FILE_NAME );
+        assertNotNull( bindings );
+        assertEquals( 1, bindings.size() );
+        assertEquals( LOCATION1, bindings.get( PID1 ) );
+    }
+
+
+    public void test_store_and_load_bindings() throws IOException
+    {
+        // ensure there is no file
+        bindingsFile.delete();
+
+        // preset bindings
+        final DynamicBindings dm0 = new DynamicBindings( new MockBundleContext(), persistenceManager );
+        dm0.putLocation( PID1, LOCATION1 );
+
+        // check bindings
+        final BundleContext ctx = new DMTestMockBundleContext();
+        final DynamicBindings dm = new DynamicBindings( ctx, persistenceManager );
+
+        // API check
+        assertEquals( LOCATION1, dm.getLocation( PID1 ) );
+
+        // low level check
+        final Dictionary bindings = getBindings( dm );
+        assertNotNull( bindings );
+        assertEquals( 1, bindings.size() );
+        assertEquals( LOCATION1, bindings.get( PID1 ) );
+    }
+
+
+    public void test_store_and_load_bindings_with_cleanup() throws IOException
+    {
+        // ensure there is no file
+        bindingsFile.delete();
+
+        // preset bindings
+        final DynamicBindings dm0 = new DynamicBindings( new MockBundleContext(), persistenceManager );
+        dm0.putLocation( PID1, LOCATION1 );
+
+        // check bindings
+        final DynamicBindings dm = new DynamicBindings( new MockBundleContext(), persistenceManager );
+
+        // API check
+        assertNull( dm.getLocation( PID1 ) );
+
+        // low level check
+        final Dictionary bindings = getBindings( dm );
+        assertNotNull( bindings );
+        assertTrue( bindings.isEmpty() );
+    }
+
+
+    private static Dictionary getBindings( DynamicBindings dm )
+    {
+        try
+        {
+            final Field bindings = dm.getClass().getDeclaredField( "bindings" );
+            bindings.setAccessible( true );
+            return ( Dictionary ) bindings.get( dm );
+        }
+        catch ( Throwable t )
+        {
+            fail( "Cannot get bindings field: " + t );
+            return null;
+        }
+    }
+
+    private static class DMTestMockBundleContext extends MockBundleContext
+    {
+        public Bundle[] getBundles()
+        {
+            return new Bundle[]
+                { new MockBundle( this, LOCATION1 ) };
+        }
+    }
+}
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 ab77a1b..9eff377 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
@@ -535,6 +535,95 @@
     }
 
 
+    @Test
+    public void test_static_binding_and_unbinding() throws BundleException
+    {
+        final String pid = "test_static_binding_and_unbinding";
+        final String location = bundleContext.getBundle().getLocation();
+
+        // create and statically bind the configuration
+        configure( pid );
+        final Configuration config = getConfiguration( pid );
+        TestCase.assertEquals( pid, config.getPid() );
+        TestCase.assertNull( config.getBundleLocation() );
+
+        // bind the configuration
+        config.setBundleLocation( location );
+        TestCase.assertEquals( location, config.getBundleLocation() );
+
+        // restart CM bundle
+        final Bundle cmBundle = getCmBundle();
+        cmBundle.stop();
+        delay();
+        cmBundle.start();
+
+        // assert configuration still bound
+        final Configuration configAfterRestart = getConfiguration( pid );
+        TestCase.assertEquals( pid, configAfterRestart.getPid() );
+        TestCase.assertEquals( location, configAfterRestart.getBundleLocation() );
+
+        // unbind the configuration
+        configAfterRestart.setBundleLocation( null );
+        TestCase.assertNull( configAfterRestart.getBundleLocation() );
+
+        // restart CM bundle
+        cmBundle.stop();
+        delay();
+        cmBundle.start();
+
+        // assert configuration unbound
+        final Configuration configUnboundAfterRestart = getConfiguration( pid );
+        TestCase.assertEquals( pid, configUnboundAfterRestart.getPid() );
+        TestCase.assertNull( configUnboundAfterRestart.getBundleLocation() );
+    }
+
+
+    @Test
+    public void test_dynamic_binding_and_unbinding() throws BundleException
+    {
+        final String pid = "test_dynamic_binding_and_unbinding";
+
+        // create and statically bind the configuration
+        configure( pid );
+        final Configuration config = getConfiguration( pid );
+        TestCase.assertEquals( pid, config.getPid() );
+        TestCase.assertNull( config.getBundleLocation() );
+
+        // dynamically bind the configuration
+        bundle = installBundle( pid );
+        final String location = bundle.getLocation();
+        bundle.start();
+        delay();
+        TestCase.assertEquals( location, config.getBundleLocation() );
+
+        // restart CM bundle
+        final Bundle cmBundle = getCmBundle();
+        cmBundle.stop();
+        delay();
+        cmBundle.start();
+
+        // assert configuration still bound
+        final Configuration configAfterRestart = getConfiguration( pid );
+        TestCase.assertEquals( pid, configAfterRestart.getPid() );
+        TestCase.assertEquals( location, configAfterRestart.getBundleLocation() );
+
+        // stop bundle (configuration remains bound !!)
+        bundle.stop();
+        delay();
+        TestCase.assertEquals( location, configAfterRestart.getBundleLocation() );
+
+        // restart CM bundle
+        cmBundle.stop();
+        delay();
+        cmBundle.start();
+
+        // assert configuration still bound
+        final Configuration configBoundAfterRestart = getConfiguration( pid );
+        TestCase.assertEquals( pid, configBoundAfterRestart.getPid() );
+        TestCase.assertEquals( location, configBoundAfterRestart.getBundleLocation() );
+    }
+
+
     /*
     @Test
     public void test_() throws BundleException