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/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