You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2015/01/02 12:05:28 UTC

svn commit: r1648997 - in /sling/trunk/installer: core/src/main/java/org/apache/sling/installer/api/ core/src/main/java/org/apache/sling/installer/core/impl/ factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/...

Author: cziegeler
Date: Fri Jan  2 11:05:28 2015
New Revision: 1648997

URL: http://svn.apache.org/r1648997
Log:
SLING-4272 : Issues in handling of configurations wrt update handling and write back

Modified:
    sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java
    sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java
    sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
    sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
    sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java

Modified: sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java (original)
+++ sling/trunk/installer/core/src/main/java/org/apache/sling/installer/api/ResourceChangeListener.java Fri Jan  2 11:05:28 2015
@@ -38,6 +38,17 @@ import aQute.bnd.annotation.ProviderType
 public interface ResourceChangeListener {
 
     /**
+     * This attribute defines if a change of the resource should be persisted by the
+     * installer. This property is a boolean value defaulting to true.
+     *
+     * The property should be used, if a resource should not be updated/deleted if
+     * the resource is modified/deleted outside of the installer, e.g. if a configuration
+     * is changed or deleted through configuration admin.
+     * @since 3.2.0
+     */
+    public static final String RESOURCE_PERSIST = "org.apache.sling.installer.api.persist";
+
+    /**
      * Inform the installer about an added or updated
      * resource
      * @param resourceType The resource type

Modified: sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java (original)
+++ sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/EntityResourceList.java Fri Jan  2 11:05:28 2015
@@ -198,6 +198,15 @@ public class EntityResourceList implemen
     }
 
     /**
+     * Force the state to be set
+     */
+    public void setForceFinishState(final ResourceState state) {
+        // We first set the state of the resource to install to make setFinishState work in all cases
+        ((RegisteredResourceImpl)this.getFirstResource()).setState(ResourceState.INSTALL);
+        this.setFinishState(state);
+    }
+
+    /**
      * @see org.apache.sling.installer.api.tasks.TaskResourceGroup#setFinishState(org.apache.sling.installer.api.tasks.ResourceState)
      */
     public void setFinishState(ResourceState state) {

Modified: sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java (original)
+++ sling/trunk/installer/core/src/main/java/org/apache/sling/installer/core/impl/OsgiInstallerImpl.java Fri Jan  2 11:05:28 2015
@@ -938,6 +938,7 @@ implements OsgiInstaller, ResourceChange
     private final List<UpdateInfo> updateInfos = new ArrayList<OsgiInstallerImpl.UpdateInfo>();
 
     /**
+     * Store the changes in an internal queue, the queue is processed in {@link #processUpdateInfos()}.
      * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map)
      */
     public void resourceAddedOrUpdated(final String resourceType,
@@ -971,7 +972,26 @@ implements OsgiInstaller, ResourceChange
         }
     }
 
+    /**
+     * Store the changes in an internal queue, the queue is processed in {@link #processUpdateInfos()}.
+     * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String)
+     */
+    public void resourceRemoved(final String resourceType, String resourceId) {
+        final UpdateInfo ui = new UpdateInfo();
+        ui.resourceType = resourceType;
+        ui.entityId = resourceId;
+
+        synchronized ( this.resourcesLock ) {
+            updateInfos.add(ui);
+            this.wakeUp();
+        }
+    }
 
+    /**
+     * Process the internal queue of updates
+     * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map)
+     * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String)
+     */
     private void processUpdateInfos() {
         final List<UpdateInfo> infos = new ArrayList<OsgiInstallerImpl.UpdateInfo>();
         synchronized ( this.resourcesLock ) {
@@ -986,33 +1006,77 @@ implements OsgiInstaller, ResourceChange
             }
         }
     }
+
+    private boolean handleExternalUpdateWithoutWriteBack(final EntityResourceList erl) {
+        final TaskResource tr = erl.getFirstResource();
+
+        // if this is an update but the change should not be persisted, we have to change
+        // the state to IGNORED
+        // or to UNINSTALLED if state is UNINSTALL
+        if ( tr.getState() == ResourceState.UNINSTALLED || tr.getState() == ResourceState.IGNORED ) {
+            // ignore
+            return false;
+        } else if ( tr.getState() == ResourceState.UNINSTALL ) {
+            erl.setFinishState(ResourceState.UNINSTALLED);
+            return true;
+        } else {
+            erl.setForceFinishState(ResourceState.IGNORED);
+            return true;
+        }
+    }
+    /**
+     * Handle external addition or update of a resource
+     * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map)
+     */
     private void internalResourceAddedOrUpdated(final String resourceType,
             final String entityId,
             final ResourceData data,
             final Dictionary<String, Object> dict,
             final Map<String, Object> attributes) {
         final String key = resourceType + ':' + entityId;
+        final boolean persistChange = (attributes != null ? PropertiesUtil.toBoolean(attributes.get(ResourceChangeListener.RESOURCE_PERSIST), true) : true);
         try {
+            boolean compactAndSave = false;
+            boolean done = false;
+
             synchronized ( this.resourcesLock ) {
                 final EntityResourceList erl = this.persistentList.getEntityResourceList(key);
                 logger.debug("Added or updated {} : {}", key, erl);
 
                 // we first check for update
-                boolean updated = false;
                 if ( erl != null && erl.getFirstResource() != null ) {
                     // check digest for dictionaries
                     final TaskResource tr = erl.getFirstResource();
                     if ( dict != null ) {
                         final String digest = FileDataStore.computeDigest(dict);
-                        if ( tr.getState() == ResourceState.INSTALLED && tr.getDigest().equals(digest) ) {
-                            logger.debug("Resource did not change {}", key);
-                            return;
+                        if ( tr.getDigest().equals(digest) ) {
+                            if ( tr.getState() == ResourceState.INSTALLED  ) {
+                                logger.debug("Resource did not change {}", key);
+                            } else if ( tr.getState() == ResourceState.INSTALL
+                                || tr.getState() == ResourceState.IGNORED ) {
+                                erl.setForceFinishState(ResourceState.INSTALLED);
+                                compactAndSave = true;
+                            }
+                            done = true;
                         }
                     }
-                    final UpdateHandler handler = this.findHandler(tr.getScheme());
-                    if ( handler == null ) {
-                        logger.debug("No handler found to handle update of resource with scheme {}", tr.getScheme());
+
+                    final UpdateHandler handler;
+                    if ( !done && persistChange ) {
+                        handler = this.findHandler(tr.getScheme());
+                        if ( handler == null ) {
+                            logger.debug("No handler found to handle update of resource with scheme {}", tr.getScheme());
+                        }
                     } else {
+                        handler = null;
+                    }
+
+                    if ( !done && handler == null ) {
+                        compactAndSave = this.handleExternalUpdateWithoutWriteBack(erl);
+                        done = true;
+                    }
+
+                    if ( !done ) {
                         final InputStream localIS = data.getInputStream();
                         try {
                             final UpdateResult result = (localIS == null ? handler.handleUpdate(resourceType, entityId, tr.getURL(), data.getDictionary(), attributes)
@@ -1051,29 +1115,26 @@ implements OsgiInstaller, ResourceChange
                                             data.getDigest(result.getURL(), result.getDigest()),
                                             result.getPriority(),
                                             result.getURL());
-                                    // We first set the state of the resource to install to make setFinishState work in all cases
-                                    ((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
-                                    erl.setFinishState(ResourceState.INSTALLED);
-                                    erl.compact();
+                                    erl.setForceFinishState(ResourceState.INSTALLED);
                                 }
-                                updated = true;
+                                compactAndSave = true;
+                            } else {
+                                // handler does not persist
+                                compactAndSave = this.handleExternalUpdateWithoutWriteBack(erl);
                             }
                         } finally {
                             if ( localIS != null ) {
                                 // always close the input stream!
-                                try {
-                                    localIS.close();
-                                } catch (final IOException ignore) {
+                                try {  localIS.close(); } catch (final IOException ignore) {
                                     // ignore
                                 }
                             }
                         }
+                        done = true;
                     }
-
                 }
 
-                boolean created = false;
-                if ( !updated ) {
+                if ( !done ) {
                     // create
                     final List<UpdateHandler> handlerList = this.updateHandlerTracker.getSortedServices();
                     for(final UpdateHandler handler : handlerList) {
@@ -1104,7 +1165,8 @@ implements OsgiInstaller, ResourceChange
                                 final EntityResourceList newGroup = this.persistentList.getEntityResourceList(key);
                                 newGroup.setFinishState(ResourceState.INSTALLED);
                                 newGroup.compact();
-                                created = true;
+                                compactAndSave = true;
+                                done = true;
                                 break;
                             }
                         } finally {
@@ -1118,14 +1180,14 @@ implements OsgiInstaller, ResourceChange
                             }
                         }
                     }
-                    if ( !created ) {
+                    if ( !done ) {
                         logger.debug("No handler found to handle creation of resource {}", key);
                     }
                 }
-                if ( updated || created ) {
+                if ( compactAndSave ) {
+                    erl.compact();
                     this.persistentList.save();
                 }
-
             }
         } catch (final IOException ioe) {
             logger.error("Unable to handle resource add or update of " + key, ioe);
@@ -1133,35 +1195,29 @@ implements OsgiInstaller, ResourceChange
     }
 
     /**
+     * Handle external removal a resource
      * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String)
      */
-    public void resourceRemoved(final String resourceType, String resourceId) {
-        final UpdateInfo ui = new UpdateInfo();
-        ui.resourceType = resourceType;
-        ui.entityId = resourceId;
-
-        synchronized ( this.resourcesLock ) {
-            updateInfos.add(ui);
-            this.wakeUp();
-        }
-    }
-
-    private void internalResourceRemoved(final String resourceType, String resourceId) {
+    private void internalResourceRemoved(final String resourceType, final String entityId) {
 
-        String key = resourceType + ':' + resourceId;
+        String key = resourceType + ':' + entityId;
         synchronized ( this.resourcesLock ) {
             final EntityResourceList erl = this.persistentList.getEntityResourceList(key);
             logger.debug("Removed {} : {}", key, erl);
             // if this is not registered at all, we can simply ignore this
             if ( erl != null ) {
-                resourceId = erl.getResourceId();
+                final String resourceId = erl.getResourceId();
                 key = resourceType + ':' + resourceId;
                 final TaskResource tr = erl.getFirstResource();
                 if ( tr != null ) {
                     if ( tr.getState() == ResourceState.IGNORED ) {
                         // if it has been ignored before, we activate it now again!
-                        ((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
-                        this.persistentList.save();
+                        // but only if it is not a template
+                        if ( tr.getDictionary() == null
+                             || tr.getDictionary().get(InstallableResource.RESOURCE_IS_TEMPLATE) == null ) {
+                            ((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL);
+                            this.persistentList.save();
+                        }
                     } else if ( tr.getState() == ResourceState.UNINSTALLED ) {
                         // it has already been removed - nothing do to
                     } else {
@@ -1173,9 +1229,7 @@ implements OsgiInstaller, ResourceChange
                         } else {
                             // we don't need to check the result, we just check if a result is returned
                             if ( handler.handleRemoval(resourceType, resourceId, tr.getURL()) != null ) {
-                                // We first set the state of the resource to uninstall to make setFinishState work in all cases
-                                ((RegisteredResourceImpl)tr).setState(ResourceState.UNINSTALL);
-                                erl.setFinishState(ResourceState.UNINSTALLED);
+                                erl.setForceFinishState(ResourceState.UNINSTALLED);
                                 erl.compact();
                             } else {
                                 // set to ignored

Modified: sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java (original)
+++ sling/trunk/installer/factories/configuration/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigTaskCreator.java Fri Jan  2 11:05:28 2015
@@ -122,24 +122,27 @@ public class ConfigTaskCreator
                     final Coordinator.Operation op = Coordinator.SHARED.get(event.getPid(), event.getFactoryPid(), false);
                     if ( config != null && op == null ) {
                         final boolean persist = ConfigUtil.toBoolean(config.getProperties().get(ConfigurationConstants.PROPERTY_PERSISTENCE), true);
-                        if ( persist ) {
-                            final Dictionary<String, Object> dict = ConfigUtil.cleanConfiguration(config.getProperties());
-                            final Map<String, Object> attrs = new HashMap<String, Object>();
-                            attrs.put(Constants.SERVICE_PID, event.getPid());
-                            if ( event.getFactoryPid() == null ) {
-                                attrs.put(InstallableResource.RESOURCE_URI_HINT, pid);
-                            } else {
-                                attrs.put(InstallableResource.RESOURCE_URI_HINT, event.getFactoryPid() + '-' + pid);
-                            }
-                            if ( config.getBundleLocation() != null ) {
-                                attrs.put(InstallableResource.INSTALLATION_HINT, config.getBundleLocation());
-                            }
-                            // Factory?
-                            if (event.getFactoryPid() != null) {
-                                attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
-                            }
-                            this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, id, null, dict, attrs);
+
+                        final Dictionary<String, Object> dict = ConfigUtil.cleanConfiguration(config.getProperties());
+                        final Map<String, Object> attrs = new HashMap<String, Object>();
+                        if ( !persist ) {
+                            attrs.put(ResourceChangeListener.RESOURCE_PERSIST, Boolean.FALSE);
                         }
+                        attrs.put(Constants.SERVICE_PID, event.getPid());
+                        if ( event.getFactoryPid() == null ) {
+                            attrs.put(InstallableResource.RESOURCE_URI_HINT, pid);
+                        } else {
+                            attrs.put(InstallableResource.RESOURCE_URI_HINT, event.getFactoryPid() + '-' + pid);
+                        }
+                        if ( config.getBundleLocation() != null ) {
+                            attrs.put(InstallableResource.INSTALLATION_HINT, config.getBundleLocation());
+                        }
+                        // Factory?
+                        if (event.getFactoryPid() != null) {
+                            attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
+                        }
+                        this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, id, null, dict, attrs);
+
                     } else {
                         this.logger.debug("Ignoring configuration event for {}:{}", event.getPid(), event.getFactoryPid());
                     }

Modified: sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java?rev=1648997&r1=1648996&r2=1648997&view=diff
==============================================================================
--- sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java (original)
+++ sling/trunk/installer/it/src/test/java/org/apache/sling/installer/it/ConfigInstallTest.java Fri Jan  2 11:05:28 2015
@@ -307,6 +307,152 @@ public class ConfigInstallTest extends O
     }
 
     @Test
+    public void testInstallUpdateRemoveConfig() throws Exception {
+        final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+        cfgData.put("foo", "bar");
+        final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
+        assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
+
+        // install config
+        final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
+                null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+        installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+        // get config
+        final Configuration cfg = waitForConfiguration("After installing", cfgPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+        // update configuration
+        final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+        secondData.put("foo", "bla");
+        cfg.update(secondData);
+
+        sleep(200);
+
+        // get updated config
+        final Configuration secondCfg = waitForConfiguration("After updating", cfgPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+        // remove config
+        secondCfg.delete();
+
+        sleep(200);
+
+        final Configuration origCfg = waitForConfiguration("After deleting", cfgPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bar", origCfg.getProperties().get("foo"));
+    }
+
+    @Test
+    public void testInstallUpdateRemoveTemplateConfig() throws Exception {
+        final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+        cfgData.put("foo", "bar");
+        cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
+        final String cfgPid = getClass().getSimpleName() + "." + uniqueID();
+        assertNull("Config " + cfgPid + " must not be found before test", findConfiguration(cfgPid));
+
+        // install config
+        final InstallableResource rsrc = new InstallableResource("/configA/" + cfgPid,
+                null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+        installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+        // get config
+        final Configuration cfg = waitForConfiguration("After installing", cfgPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+        // update configuration
+        final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+        secondData.put("foo", "bla");
+        cfg.update(secondData);
+
+        sleep(200);
+
+        // get updated config
+        final Configuration secondCfg = waitForConfiguration("After updating", cfgPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+        // remove config
+        secondCfg.delete();
+
+        sleep(200);
+
+        final Configuration noCfg = waitForConfiguration("After deleting", cfgPid, TIMEOUT, false);
+        assertNull("Configuration should be removed", noCfg);
+    }
+
+    @Test
+    public void testInstallUpdateRemoveConfigFactory() throws Exception {
+        final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+        cfgData.put("foo", "bar");
+        final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
+        final String alias = "alias" + uniqueID();
+        assertNull("Factory config " + cfgFactoryPid + " must not be found before test", findFactoryConfiguration(cfgFactoryPid));
+
+        // install factory config
+        final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
+                null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+        installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+        // get factory config
+        final Configuration cfg = waitForFactoryConfiguration("After installing", cfgFactoryPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+        // update configuration
+        final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+        secondData.put("foo", "bla");
+        cfg.update(secondData);
+
+        sleep(200);
+
+        // get updated factory config
+        final Configuration secondCfg = waitForFactoryConfiguration("After updating", cfgFactoryPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+        // remove factory config
+        secondCfg.delete();
+        sleep(200);
+
+        final Configuration origCfg = waitForFactoryConfiguration("After deleting", cfgFactoryPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bar", origCfg.getProperties().get("foo"));
+    }
+
+    @Test
+    public void testInstallUpdateRemoveTemplateConfigFactory() throws Exception {
+        final Dictionary<String, Object> cfgData = new Hashtable<String, Object>();
+        cfgData.put("foo", "bar");
+        cfgData.put(InstallableResource.RESOURCE_IS_TEMPLATE, "true");
+        final String cfgFactoryPid = getClass().getSimpleName() + "." + uniqueID();
+        final String alias = "alias" + uniqueID();
+        assertNull("Factory config " + cfgFactoryPid + " must not be found before test", findFactoryConfiguration(cfgFactoryPid));
+
+        // install factory config
+        final InstallableResource rsrc = new InstallableResource("/configA/" + cfgFactoryPid + "-" + alias,
+                null, cfgData, null, InstallableResource.TYPE_PROPERTIES, 10);
+        installer.updateResources(URL_SCHEME, new InstallableResource[] {rsrc}, null);
+
+        // get factory config
+        final Configuration cfg = waitForFactoryConfiguration("After installing", cfgFactoryPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bar", cfg.getProperties().get("foo"));
+
+        // update configuration
+        final Dictionary<String, Object> secondData = new Hashtable<String, Object>();
+        secondData.put("foo", "bla");
+        cfg.update(secondData);
+
+        sleep(200);
+
+        // get updated factory config
+        final Configuration secondCfg = waitForFactoryConfiguration("After updating", cfgFactoryPid, TIMEOUT, true);
+        assertEquals("Config value must match", "bla", secondCfg.getProperties().get("foo"));
+
+        // remove config
+        secondCfg.delete();
+        sleep(200);
+
+        final Configuration noCfg = waitForFactoryConfiguration("After deleting", cfgFactoryPid, TIMEOUT, false);
+        assertNull("Factory configuration should be removed", noCfg);
+    }
+
+    @Test
     public void testDeferredConfigInstall() throws Exception {
         // get config admin bundle and wait for service
     	final Bundle configAdmin = this.getConfigAdminBundle();