SLING-7786 : Use R7 configuration admin supporting named factory configurations....
authorCarsten Ziegeler <cziegele@adobe.com>
Thu, 9 Aug 2018 07:29:19 +0000 (09:29 +0200)
committerCarsten Ziegeler <cziegele@adobe.com>
Thu, 9 Aug 2018 07:29:19 +0000 (09:29 +0200)
src/test/java/org/apache/sling/installer/it/ConfigUpdateTest.java
src/test/java/org/apache/sling/installer/it/ConfigUpdateTestUtil.java [new file with mode: 0644]

index 650a332..1477f8d 100644 (file)
  */
 package org.apache.sling.installer.it;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.util.Dictionary;
 import java.util.Hashtable;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sling.installer.api.InstallableResource;
-import org.apache.sling.installer.api.event.InstallationEvent;
-import org.apache.sling.installer.api.event.InstallationListener;
-import org.apache.sling.installer.api.info.InfoProvider;
-import org.apache.sling.installer.api.info.InstallationState;
-import org.apache.sling.installer.api.info.Resource;
-import org.apache.sling.installer.api.info.ResourceGroup;
+
 import org.apache.sling.installer.api.tasks.ResourceState;
-import org.apache.sling.installer.api.tasks.TaskResource;
-import static org.junit.Assert.*;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.junit.PaxExam;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
 
@@ -55,149 +39,30 @@ import org.osgi.service.cm.ConfigurationAdmin;
 @RunWith(PaxExam.class)
 public class ConfigUpdateTest extends OsgiInstallerTestBase {
 
-    static private final String GROUP_ID = "org.apache.sling";
-    static private final String ARTIFACT_ID = "org.apache.sling.installer.factory.configuration";
-    static private final String OLD_VERSION = "1.1.2";
+    private ConfigUpdateTestUtil util;
 
-    private static final String FACTORY_PID = "org.apache.sling.factory.test";
-    private static final String NAME_1 = "myname1";
-
-    private static final String SCHEME = "myscheme";
+    private ConfigurationAdmin configAdmin;
 
     @org.ops4j.pax.exam.Configuration
     public Option[] config() {
         return defaultConfiguration();
     }
 
-    private Bundle getConfigFactoryBundle() {
-        for(final Bundle b : this.bundleContext.getBundles()) {
-            if ( ARTIFACT_ID.equals(b.getSymbolicName())) {
-                return b;
-            }
-        }
-        throw new IllegalStateException("Config factory bundle not found");
-    }
-
-    private void updateConfigFactoryBundle() throws Exception {
-        final Bundle b = getConfigFactoryBundle();
-        b.stop();
-        final String urlString = org.ops4j.pax.exam.CoreOptions.mavenBundle(GROUP_ID, ARTIFACT_ID, OsgiInstallerTestBase.CONFIG_VERSION).getURL();
-        final URL url = new URL(urlString);
-        try ( final InputStream is = url.openStream()) {
-            b.update(is);
-        }
-        b.start();
-    }
-
     @Before
     public void setUp() throws Exception {
-        // we need the old config factory first
-        final Bundle b = getConfigFactoryBundle();
-        b.stop();
-        final String urlString = org.ops4j.pax.exam.CoreOptions.mavenBundle(GROUP_ID, ARTIFACT_ID, OLD_VERSION).getURL();
-        final URL url = new URL(urlString);
-        try ( final InputStream is = url.openStream()) {
-            b.update(is);
-        }
-        b.start();
+        // instantiate util and downgrade factory config bundle to 1.x
+        this.util = new ConfigUpdateTestUtil(this.bundleContext);
+
         super.setup();
         setupInstaller();
-    }
-
-    private InstallableResource[] createTestConfigs() {
-        final Dictionary<String, Object> props = new Hashtable<>();
-        props.put("key", "value");
-        props.put("id", NAME_1);
-
-        // we need to specify a path as config factory < 1.2.0 has a bug in handling the id if a path is missing
-        final InstallableResource rsrc = new InstallableResource("configs/" + FACTORY_PID + "-" + NAME_1 + ".cfg",
-                null, props, "1", InstallableResource.TYPE_CONFIG, null);
-
-        return new InstallableResource[] {rsrc};
-    }
-
-    private Configuration assertConfig(final String name, final boolean checkNew) throws Exception {
-        return assertConfig(name, checkNew, checkNew);
-    }
-
-    private Configuration assertConfig(final String name, final boolean checkNew, final boolean modifiedExists) throws Exception {
-        final ConfigurationAdmin ca = this.getService(ConfigurationAdmin.class);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-            "(id=" + name + "))");
-        assertNotNull(cfgs);
-        assertEquals(1, cfgs.length);
-        final Configuration c = cfgs[0];
-        assertEquals("value", c.getProperties().get("key"));
-        assertEquals(name, c.getProperties().get("id"));
-
-        if ( !checkNew) {
-            assertFalse(c.getPid().equals(FACTORY_PID + "~" + name));
-            final Configuration[] cfgs1 = ca.listConfigurations("(" + Constants.SERVICE_PID + "=" + FACTORY_PID + "~" + name + ")");
-            assertTrue(cfgs1 == null || cfgs1.length == 0);
-        }
-        if ( checkNew) {
-            final Configuration[] cfgs1 = ca.listConfigurations("(" + Constants.SERVICE_PID + "=" + FACTORY_PID + "~" + name + ")");
-            assertNotNull(cfgs1);
-            assertEquals(1, cfgs1.length);
-            final Configuration c1 = cfgs1[0];
-            assertEquals("value", c1.getProperties().get("key"));
-            assertEquals(name, c1.getProperties().get("id"));
-            assertEquals(FACTORY_PID, c1.getFactoryPid());
-            assertEquals(c.getPid(), c1.getPid());
-        }
-        if ( modifiedExists ) {
-            assertEquals(Boolean.TRUE, c.getProperties().get("modified"));
-        } else {
-            assertNull(c.getProperties().get("modified"));
-        }
-        return c;
-    }
-
-    private void assertInstallerState(final String name, final boolean checkNew, final ResourceState expectedState) throws Exception {
-        // make sure there is only one state in the OSGi installer
-        final InfoProvider infoProvider = getService(InfoProvider.class);
-        ResourceGroup found = null;
-        final InstallationState state = infoProvider.getInstallationState();
-        for(final ResourceGroup group : state.getInstalledResources()) {
-            for(final Resource rsrc : group.getResources()) {
-                if ( rsrc.getScheme().equals(SCHEME) && rsrc.getURL().equals(SCHEME + ":" + "configs/" + FACTORY_PID + "-" + name + ".cfg")) {
-                    found = group;
-                    break;
-                }
-            }
-            if ( found != null ) {
-                break;
-            }
-        }
-        assertNotNull(found);
-        assertEquals(1, found.getResources().size());
-        final Resource r = found.getResources().get(0);
-        if ( checkNew ) {
-            assertEquals("config:" + FACTORY_PID + "~" + name, r.getEntityId());
-        } else {
-            assertEquals("config:" + FACTORY_PID + "." + name, r.getEntityId());
-        }
-        assertEquals(expectedState, r.getState());
+        this.configAdmin = this.waitForConfigAdmin(true);
+        this.util.init(this.configAdmin, this.installer, this.infoProvider);
     }
 
     @Override
     @After
     public void tearDown() {
-
-        try {
-            // stop configuration factory
-            getConfigFactoryBundle().stop();
-
-            // remove all configurations
-            final Configuration[] cfgs = this.getService(ConfigurationAdmin.class).listConfigurations(null);
-            if ( cfgs != null ) {
-                for(final Configuration c : cfgs) {
-                    c.delete();
-                }
-            }
-        } catch ( final IOException | BundleException | InvalidSyntaxException ignore) {
-            // ignore
-        }
+        this.util.tearDown();
         super.tearDown();
     }
 
@@ -205,93 +70,58 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
      * Simply updating the bundle should not change anything
      */
     @Test public void testBundleUpdate() throws Exception {
-        final InstallableResource[] resources = createTestConfigs();
-        final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
-        final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
-        try {
-            installer.registerResources(SCHEME, resources);
-
-            listener.waitForInstall();
-        } finally {
-            reg.unregister();
-        }
+        this.util.installTestConfigs();
+
         // check for configuration
-        assertConfig(NAME_1, false);
-        assertInstallerState(NAME_1, false, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, false);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, false, ResourceState.INSTALLED);
 
-        updateConfigFactoryBundle();
+        this.util.updateConfigFactoryBundle();
 
-        assertConfig(NAME_1, false);
-        assertInstallerState(NAME_1, false, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, false);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, false, ResourceState.INSTALLED);
     }
 
     /**
      * Simply updating the bundle and then updating the config with the same contents should not change anything
      */
     @Test public void testBundleAndConfigRegisterWithoutChange() throws Exception {
-        testBundleUpdate();
+        this.testBundleUpdate();
 
-        final InstallableResource[] resources = createTestConfigs();
-        final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
-        final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
-        try {
-            installer.registerResources(SCHEME, resources);
+        // register configuration again (unchanged)
+        this.util.installTestConfigs();
 
-            listener.waitForInstall();
-        } finally {
-            reg.unregister();
-        }
         // check for configuration
-        assertConfig(NAME_1, false);
-        assertInstallerState(NAME_1, false, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, false);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, false, ResourceState.INSTALLED);
     }
 
     /**
      * Updating the bundle and then updating the config with a new config should convert the configurations
      */
     @Test public void testBundleAndConfigRegisterWithChange() throws Exception {
-        testBundleUpdate();
+        this.testBundleUpdate();
+
+        // register configuration again (changed - using registerResources)
+        this.util.installModifiedTestConfigs(true);
 
-        final InstallableResource[] resources = createTestConfigs();
-        for(final InstallableResource rsrc : resources) {
-            rsrc.getDictionary().put("modified", Boolean.TRUE);
-        }
-        final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
-        final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
-        try {
-            installer.registerResources(SCHEME, resources);
-
-            listener.waitForInstall();
-        } finally {
-            reg.unregister();
-        }
         // check for configuration
-        assertConfig(NAME_1, true);
-        assertInstallerState(NAME_1, true, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, true);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.INSTALLED);
     }
 
     /**
      * Updating the bundle and then updating the config with a new config should convert the configurations
      */
     @Test public void testBundleAndConfigUpdateWithChange() throws Exception {
-        testBundleUpdate();
+        this.testBundleUpdate();
+
+        // register configuration again (changed - using updateResources)
+        this.util.installModifiedTestConfigs(false);
 
-        final InstallableResource[] resources = createTestConfigs();
-        for(final InstallableResource rsrc : resources) {
-            rsrc.getDictionary().put("modified", Boolean.TRUE);
-        }
-        final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
-        final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
-        try {
-            installer.updateResources(SCHEME, resources, null);
-
-            listener.waitForInstall();
-        } finally {
-            reg.unregister();
-        }
         // check for configuration
-        assertConfig(NAME_1, true);
-        assertInstallerState(NAME_1, true, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, true);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.INSTALLED);
     }
 
     /**
@@ -301,11 +131,10 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
      * - manual update of that configuration through config admin
      */
     @Test public void testManualUpdateWithoutConversion() throws Exception {
-        testBundleUpdate();
+        this.testBundleUpdate();
 
-        final ConfigurationAdmin ca = this.waitForConfigAdmin(true);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         // we know it's just one config
         final Configuration c = cfgs[0];
         final Dictionary<String, Object> props = c.getProperties();
@@ -315,9 +144,9 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
         // the update is processed async, so we should give the installer parts some time to process
         this.sleep(5000); // TODO - Can we wait for an event instead?
 
-        final Configuration cUp = assertConfig(NAME_1, true, false);
+        final Configuration cUp = this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, true, false);
         assertEquals("helloworld", cUp.getProperties().get("another"));
-        assertInstallerState(NAME_1, true, ResourceState.IGNORED);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.IGNORED);
     }
 
     /**
@@ -329,9 +158,8 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
     @Test public void testManualDeleteWithoutConversion() throws Exception {
         testBundleUpdate();
 
-        final ConfigurationAdmin ca = this.waitForConfigAdmin(true);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         // we know it's just one config
         final Configuration c = cfgs[0];
         c.delete();
@@ -340,11 +168,11 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
         this.sleep(5000); // TODO - Can we wait for an event instead?
 
         // config should be deleted
-        final Configuration[] cfgs2 = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs2 = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         assertTrue(cfgs2 == null || cfgs2.length == 0);
         // state should be ignored
-        assertInstallerState(NAME_1, true, ResourceState.IGNORED);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.IGNORED);
     }
 
     /**
@@ -357,9 +185,8 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
     @Test public void testManualUpdateAndDeleteWithoutConversion() throws Exception {
         testManualUpdateWithoutConversion();
 
-        final ConfigurationAdmin ca = this.waitForConfigAdmin(true);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         // we know it's just one config
         final Configuration c = cfgs[0];
         c.delete();
@@ -368,8 +195,8 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
         this.sleep(5000); // TODO - Can we wait for an event instead?
 
         // config should be reverted
-        assertConfig(NAME_1, true, false);
-        assertInstallerState(NAME_1, true, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, true, false);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.INSTALLED);
     }
 
     /**
@@ -382,9 +209,8 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
     @Test public void testManualUpdateAfterConversion() throws Exception {
         testBundleAndConfigUpdateWithChange();
 
-        final ConfigurationAdmin ca = this.waitForConfigAdmin(true);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         // we know it's just one config
         final Configuration c = cfgs[0];
         final Dictionary<String, Object> props = c.getProperties();
@@ -394,9 +220,9 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
         // the update is processed async, so we should give the installer parts some time to process
         this.sleep(5000); // TODO - Can we wait for an event instead?
 
-        final Configuration cUp = assertConfig(NAME_1, true);
+        final Configuration cUp = this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, true);
         assertEquals("helloworld", cUp.getProperties().get("another"));
-        assertInstallerState(NAME_1, true, ResourceState.IGNORED);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.IGNORED);
     }
 
     /**
@@ -407,11 +233,10 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
      * - manual delete of that configuration through config admin
      */
     @Test public void testManualDeleteAfterConversion() throws Exception {
-        testBundleAndConfigUpdateWithChange();
+        this.testBundleAndConfigUpdateWithChange();
 
-        final ConfigurationAdmin ca = this.waitForConfigAdmin(true);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         // we know it's just one config
         final Configuration c = cfgs[0];
         c.delete();
@@ -420,11 +245,11 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
         this.sleep(5000); // TODO - Can we wait for an event instead?
 
         // config should be deleted
-        final Configuration[] cfgs2 = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs2 = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         assertTrue(cfgs2 == null || cfgs2.length == 0);
         // state should be ignored
-        assertInstallerState(NAME_1, true, ResourceState.IGNORED);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.IGNORED);
     }
 
     /**
@@ -438,9 +263,8 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
     @Test public void testManualUpdateAndDeleteAfterConversion() throws Exception {
         testManualUpdateAfterConversion();
 
-        final ConfigurationAdmin ca = this.waitForConfigAdmin(true);
-        final Configuration[] cfgs = ca.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
-                "(id=" + NAME_1 + "))");
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.FACTORY_PID + ")" +
+                "(id=" + ConfigUpdateTestUtil.NAME_1 + "))");
         // we know it's just one config
         final Configuration c = cfgs[0];
         c.delete();
@@ -449,64 +273,53 @@ public class ConfigUpdateTest extends OsgiInstallerTestBase {
         this.sleep(5000); // TODO - Can we wait for an event instead?
 
         // config should be reverted
-        assertConfig(NAME_1, true);
-        assertInstallerState(NAME_1, true, ResourceState.INSTALLED);
+        this.util.assertTestConfig(ConfigUpdateTestUtil.NAME_1, true);
+        this.util.assertInstallerState(ConfigUpdateTestUtil.NAME_1, true, ResourceState.INSTALLED);
     }
 
-    private class ResourceInstallationListener implements InstallationListener {
-
-        private final AtomicInteger processedBundles = new AtomicInteger(0);
-        private final AtomicBoolean doneProcessing = new AtomicBoolean(false);
-
-        private final int count;
-
-        public ResourceInstallationListener(final int count) {
-            this.count = count;
-        }
-
-        @Override
-        public void onEvent(final InstallationEvent event) {
-            if ( event.getType() == InstallationEvent.TYPE.PROCESSED ) {
-                final TaskResource rsrc = (TaskResource) event.getSource();
-                if ( rsrc.getScheme().equals(SCHEME) ) {
-                    if ( rsrc.getState() == ResourceState.IGNORED || rsrc.getState() == ResourceState.INSTALLED ) {
-                        processedBundles.incrementAndGet();
-                    }
-                }
-            } else if ( event.getType() == InstallationEvent.TYPE.SUSPENDED && processedBundles.get() > 0 ) {
-                doneProcessing.set(true);
-            }
+    /**
+     * Create a factory configuration before the update
+     */
+    @Test public void testManualConfigurations() throws Exception {
+        this.util.installTestConfigs();
 
+        // create two factory configurations, one with R6 and one with R7 API
+        final Configuration c1 = this.configAdmin.getFactoryConfiguration(ConfigUpdateTestUtil.MANUAL_FACTORY_PID, "c1", null);
+        final Dictionary<String, Object> props = new Hashtable<>();
+        props.put("id", "c1");
+        c1.update(props);
+        final Configuration c2 = this.configAdmin.createFactoryConfiguration(ConfigUpdateTestUtil.MANUAL_FACTORY_PID, null);
+        props.put("id", "c2");
+        c2.update(props);
+
+        this.util.updateConfigFactoryBundle();
+
+        // there should still be exactly two factory configs
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.MANUAL_FACTORY_PID + ")");
+        assertEquals(2, cfgs.length);
+
+        // and no installer state
+        this.util.assertInstallerState(ConfigUpdateTestUtil.MANUAL_FACTORY_PID, 0);
+
+        // updating the configs should not change the state
+        for(final Configuration c : cfgs) {
+            final Dictionary<String, Object> p = c.getProperties();
+            p.put("modified", "true");
+            c.update(p);
         }
-
-        public void waitForInstall() {
-            final long startTime = System.currentTimeMillis();
-            while ( !doneProcessing.get() && startTime + 10000 > System.currentTimeMillis() ) {
-                sleep(200);
-            }
-            if ( processedBundles.get() < count ) {
-                final InfoProvider infoProvider = getService(InfoProvider.class);
-                int bundlesCount = 0;
-                while ( bundlesCount < count ) {
-                    bundlesCount = 0;
-                    final InstallationState state = infoProvider.getInstallationState();
-                    for(final ResourceGroup group : state.getInstalledResources()) {
-                        for(final Resource rsrc : group.getResources()) {
-                            if ( rsrc.getScheme().equals(SCHEME) ) {
-                                bundlesCount++;
-                            }
-                        }
-                    }
-                    for(final ResourceGroup group : state.getActiveResources()) {
-                        for(final Resource rsrc : group.getResources()) {
-                            if ( rsrc.getScheme().equals(SCHEME) ) {
-                                bundlesCount++;
-                            }
-                        }
-                    }
-                    sleep(200);
-                }
-            }
+        // still two configurations
+        final Configuration[] cfgs2 = this.configAdmin.listConfigurations("(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.MANUAL_FACTORY_PID + ")");
+        assertEquals(2, cfgs2.length);
+        // and no installer state
+        this.util.assertInstallerState(ConfigUpdateTestUtil.MANUAL_FACTORY_PID, 0);
+
+        // delete should not change installer state
+        for(final Configuration c : cfgs2) {
+            c.delete();
         }
+        final Configuration[] cfgs3 = this.configAdmin.listConfigurations("(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + ConfigUpdateTestUtil.MANUAL_FACTORY_PID + ")");
+        assertTrue(cfgs3 == null || cfgs3.length == 0);
+        // and no installer state
+        this.util.assertInstallerState(ConfigUpdateTestUtil.MANUAL_FACTORY_PID, 0);
     }
 }
diff --git a/src/test/java/org/apache/sling/installer/it/ConfigUpdateTestUtil.java b/src/test/java/org/apache/sling/installer/it/ConfigUpdateTestUtil.java
new file mode 100644 (file)
index 0000000..29374c1
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * 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.sling.installer.it;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sling.installer.api.InstallableResource;
+import org.apache.sling.installer.api.OsgiInstaller;
+import org.apache.sling.installer.api.event.InstallationEvent;
+import org.apache.sling.installer.api.event.InstallationListener;
+import org.apache.sling.installer.api.info.InfoProvider;
+import org.apache.sling.installer.api.info.InstallationState;
+import org.apache.sling.installer.api.info.Resource;
+import org.apache.sling.installer.api.info.ResourceGroup;
+import org.apache.sling.installer.api.tasks.ResourceState;
+import org.apache.sling.installer.api.tasks.TaskResource;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+/**
+ * Utility methods for {@link ConfigUpdateTest}.
+ */
+public class ConfigUpdateTestUtil {
+
+    static private final String GROUP_ID = "org.apache.sling";
+    static private final String ARTIFACT_ID = "org.apache.sling.installer.factory.configuration";
+    static private final String OLD_VERSION = "1.1.2";
+
+    public static final String FACTORY_PID = "org.apache.sling.factory.test.installer";
+    public static final String MANUAL_FACTORY_PID = "org.apache.sling.factory.test.manual";
+
+    public static final String NAME_1 = "myname1";
+
+    public static final String SCHEME = "myscheme";
+
+    private final BundleContext bundleContext;
+
+    private ConfigurationAdmin configAdmin;
+
+    private InfoProvider infoProvider;
+
+    private OsgiInstaller installer;
+    
+    public ConfigUpdateTestUtil(final BundleContext ctx) throws Exception {
+        this.bundleContext = ctx;
+        // we need the old config factory first
+        final Bundle b = getConfigFactoryBundle();
+        b.stop();
+        final String urlString = org.ops4j.pax.exam.CoreOptions.mavenBundle(GROUP_ID, ARTIFACT_ID, OLD_VERSION).getURL();
+        final URL url = new URL(urlString);
+        try ( final InputStream is = url.openStream()) {
+            b.update(is);
+        }
+        b.start();
+    }
+
+    public void init(final ConfigurationAdmin configAdmin, 
+            final OsgiInstaller installer, 
+            final InfoProvider infoProvider) {
+        this.configAdmin = configAdmin;
+        this.installer = installer;
+        this.infoProvider = infoProvider;
+    }
+
+    /**
+     * Helper method for sleeping.
+     */
+    private void sleep(long msec) {
+        try {
+            Thread.sleep(msec);
+        } catch(InterruptedException ignored) {
+        }
+    }
+
+    private Bundle getConfigFactoryBundle() {
+        for(final Bundle b : this.bundleContext.getBundles()) {
+            if ( ARTIFACT_ID.equals(b.getSymbolicName())) {
+                return b;
+            }
+        }
+        throw new IllegalStateException("Config factory bundle not found");
+    }
+
+    public void updateConfigFactoryBundle() throws Exception {
+        final Bundle b = getConfigFactoryBundle();
+        b.stop();
+        final String urlString = org.ops4j.pax.exam.CoreOptions.mavenBundle(GROUP_ID, ARTIFACT_ID, OsgiInstallerTestBase.CONFIG_VERSION).getURL();
+        final URL url = new URL(urlString);
+        try ( final InputStream is = url.openStream()) {
+            b.update(is);
+        }
+        b.start();
+    }
+
+    public InstallableResource[] createTestConfigResources() {
+        final Dictionary<String, Object> props = new Hashtable<>();
+        props.put("key", "value");
+        props.put("id", NAME_1);
+
+        // we need to specify a path as config factory < 1.2.0 has a bug in handling the id if a path is missing
+        final InstallableResource rsrc = new InstallableResource("configs/" + FACTORY_PID + "-" + NAME_1 + ".cfg",
+                null, props, "1", InstallableResource.TYPE_CONFIG, null);
+
+        return new InstallableResource[] {rsrc};
+    }
+
+    public void installTestConfigs() {
+        final InstallableResource[] resources = createTestConfigResources();
+        final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
+        final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
+        try {
+            installer.registerResources(SCHEME, resources);
+
+            listener.waitForInstall();
+        } finally {
+            reg.unregister();
+        }
+    }
+
+    public void installModifiedTestConfigs(final boolean useRegister) {
+        final InstallableResource[] resources = createTestConfigResources();
+        for(final InstallableResource rsrc : resources) {
+            rsrc.getDictionary().put("modified", Boolean.TRUE);
+        }
+        final ResourceInstallationListener listener = new ResourceInstallationListener(resources.length);
+        final ServiceRegistration<InstallationListener> reg = this.bundleContext.registerService(InstallationListener.class, listener, null);
+        try {
+            if ( useRegister) {
+                installer.registerResources(SCHEME, resources);
+            } else {
+                installer.updateResources(SCHEME, resources, null);
+            }
+            listener.waitForInstall();
+        } finally {
+            reg.unregister();
+        }        
+    }
+    
+    public Configuration assertTestConfig(final String name, final boolean checkNew) throws Exception {
+        return assertTestConfig(name, checkNew, checkNew);
+    }
+
+    public Configuration assertTestConfig(final String name, final boolean checkNew, final boolean modifiedExists) throws Exception {
+        final Configuration[] cfgs = this.configAdmin.listConfigurations("(&(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + FACTORY_PID + ")" +
+            "(id=" + name + "))");
+        assertNotNull(cfgs);
+        assertEquals(1, cfgs.length);
+        final Configuration c = cfgs[0];
+        assertEquals("value", c.getProperties().get("key"));
+        assertEquals(name, c.getProperties().get("id"));
+
+        if ( !checkNew) {
+            assertFalse(c.getPid().equals(FACTORY_PID + "~" + name));
+            final Configuration[] cfgs1 = this.configAdmin.listConfigurations("(" + Constants.SERVICE_PID + "=" + FACTORY_PID + "~" + name + ")");
+            assertTrue(cfgs1 == null || cfgs1.length == 0);
+        }
+        if ( checkNew) {
+            final Configuration[] cfgs1 = this.configAdmin.listConfigurations("(" + Constants.SERVICE_PID + "=" + FACTORY_PID + "~" + name + ")");
+            assertNotNull(cfgs1);
+            assertEquals(1, cfgs1.length);
+            final Configuration c1 = cfgs1[0];
+            assertEquals("value", c1.getProperties().get("key"));
+            assertEquals(name, c1.getProperties().get("id"));
+            assertEquals(FACTORY_PID, c1.getFactoryPid());
+            assertEquals(c.getPid(), c1.getPid());
+        }
+        if ( modifiedExists ) {
+            assertEquals(Boolean.TRUE, c.getProperties().get("modified"));
+        } else {
+            assertNull(c.getProperties().get("modified"));
+        }
+        return c;
+    }
+
+    public void assertInstallerState(final String name, final boolean checkNew, final ResourceState expectedState) throws Exception {
+        // make sure there is only one state in the OSGi installer
+        ResourceGroup found = null;
+        final InstallationState state = this.infoProvider.getInstallationState();
+        for(final ResourceGroup group : state.getInstalledResources()) {
+            for(final Resource rsrc : group.getResources()) {
+                if ( rsrc.getScheme().equals(SCHEME) && rsrc.getURL().equals(SCHEME + ":" + "configs/" + FACTORY_PID + "-" + name + ".cfg")) {
+                    found = group;
+                    break;
+                }
+            }
+            if ( found != null ) {
+                break;
+            }
+        }
+        assertNotNull(found);
+        assertEquals(1, found.getResources().size());
+        final Resource r = found.getResources().get(0);
+        if ( checkNew ) {
+            assertEquals("config:" + FACTORY_PID + "~" + name, r.getEntityId());
+        } else {
+            assertEquals("config:" + FACTORY_PID + "." + name, r.getEntityId());
+        }
+        assertEquals(expectedState, r.getState());
+    }
+
+    public void assertInstallerState(final String factoryPID, final int count) throws Exception {
+        ResourceGroup found = null;
+        int c = 0;
+        final InstallationState state = this.infoProvider.getInstallationState();
+        for(final ResourceGroup group : state.getInstalledResources()) {
+            for(final Resource rsrc : group.getResources()) {
+                // contains is not precise but should be could enough
+                if (rsrc.getEntityId().startsWith("config:") && rsrc.getEntityId().contains(factoryPID) ) {
+                    c++;
+                }
+            }
+        }
+        assertEquals(count, c);
+    }
+
+    public void tearDown() {
+        try {
+            // stop configuration factory
+            getConfigFactoryBundle().stop();
+
+            // remove all configurations
+            final Configuration[] cfgs = this.configAdmin.listConfigurations(null);
+            if ( cfgs != null ) {
+                for(final Configuration c : cfgs) {
+                    c.delete();
+                }
+            }
+        } catch ( final IOException | BundleException | InvalidSyntaxException ignore) {
+            // ignore
+        }
+    }
+
+
+    private class ResourceInstallationListener implements InstallationListener {
+
+        private final AtomicInteger processedBundles = new AtomicInteger(0);
+        private final AtomicBoolean doneProcessing = new AtomicBoolean(false);
+
+        private final int count;
+
+        public ResourceInstallationListener(final int count) {
+            this.count = count;
+        }
+
+        @Override
+        public void onEvent(final InstallationEvent event) {
+            if ( event.getType() == InstallationEvent.TYPE.PROCESSED ) {
+                final TaskResource rsrc = (TaskResource) event.getSource();
+                if ( rsrc.getScheme().equals(SCHEME) ) {
+                    if ( rsrc.getState() == ResourceState.IGNORED || rsrc.getState() == ResourceState.INSTALLED ) {
+                        processedBundles.incrementAndGet();
+                    }
+                }
+            } else if ( event.getType() == InstallationEvent.TYPE.SUSPENDED && processedBundles.get() > 0 ) {
+                doneProcessing.set(true);
+            }
+
+        }
+
+        public void waitForInstall() {
+            final long startTime = System.currentTimeMillis();
+            while ( !doneProcessing.get() && startTime + 10000 > System.currentTimeMillis() ) {
+                sleep(200);
+            }
+            if ( processedBundles.get() < count ) {
+                int bundlesCount = 0;
+                while ( bundlesCount < count ) {
+                    bundlesCount = 0;
+                    final InstallationState state = infoProvider.getInstallationState();
+                    for(final ResourceGroup group : state.getInstalledResources()) {
+                        for(final Resource rsrc : group.getResources()) {
+                            if ( rsrc.getScheme().equals(SCHEME) ) {
+                                bundlesCount++;
+                            }
+                        }
+                    }
+                    for(final ResourceGroup group : state.getActiveResources()) {
+                        for(final Resource rsrc : group.getResources()) {
+                            if ( rsrc.getScheme().equals(SCHEME) ) {
+                                bundlesCount++;
+                            }
+                        }
+                    }
+                    sleep(200);
+                }
+            }
+        }
+    }
+}