FELIX-3780 - allow DA to be configured through ConfigAdmin:

- missed a system property used to process preprocessed resources;
- refactored the single property into a configuration object to allow future
  extensions without an explosion of methods on DeploymentAdminImpl.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1589435 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminConfig.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminConfig.java
new file mode 100644
index 0000000..dcfb9cc
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminConfig.java
@@ -0,0 +1,128 @@
+/*
+ * 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.deploymentadmin;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.deploymentadmin.DeploymentAdmin;
+
+/**
+ * Provides the configuration options for this implementation of {@link DeploymentAdmin}.
+ */
+public class DeploymentAdminConfig {
+    /** Configuration key used to stop only bundles mentioned in a DP instead of all bundles. */
+    static final String KEY_STOP_UNAFFECTED_BUNDLE = "stopUnaffectedBundle";
+    /** Configuration key used to allow usage of customizers outside a DP. */
+    static final String KEY_ALLOW_FOREIGN_CUSTOMIZERS = "allowForeignCustomizers";
+
+    static final boolean DEFAULT_STOP_UNAFFECTED_BUNDLE = true;
+    static final boolean DEFAULT_ALLOW_FOREIGN_CUSTOMIZERS = false;
+
+    private final BundleContext m_context;
+    private final Boolean m_stopUnaffectedBundles;
+    private final Boolean m_allowForeignCustomizers;
+
+    /**
+     * Creates a new {@link DeploymentAdminConfig} instance with the default settings.
+     */
+    public DeploymentAdminConfig(BundleContext context) {
+        m_context = context;
+        m_stopUnaffectedBundles = null;
+        m_allowForeignCustomizers = null;
+    }
+
+    /**
+     * Creates a new {@link DeploymentAdminConfig} instance with the given configuration properties.
+     */
+    public DeploymentAdminConfig(BundleContext context, Dictionary properties) throws ConfigurationException {
+        Boolean stopUnaffectedBundles = null;
+        Boolean allowForeignCustomizers = null;
+
+        if (properties != null) {
+            stopUnaffectedBundles = getMandatoryValue(properties, KEY_STOP_UNAFFECTED_BUNDLE);
+            allowForeignCustomizers = getMandatoryValue(properties, KEY_ALLOW_FOREIGN_CUSTOMIZERS);
+        }
+
+        m_context = context;
+        m_stopUnaffectedBundles = stopUnaffectedBundles;
+        m_allowForeignCustomizers = allowForeignCustomizers;
+    }
+
+    /**
+     * Creates a new {@link DeploymentAdminConfig} instance as copy of the given configuration.
+     */
+    public DeploymentAdminConfig(DeploymentAdminConfig configuration) {
+        m_context = configuration.m_context;
+        m_stopUnaffectedBundles = configuration.m_stopUnaffectedBundles;
+        m_allowForeignCustomizers = configuration.m_allowForeignCustomizers;
+    }
+
+    private static Boolean getMandatoryValue(Dictionary dict, String key) throws ConfigurationException {
+        Object value = dict.get(key);
+        if (value == null || !(value instanceof String || value instanceof Boolean)) {
+            throw new ConfigurationException(key, "missing or invalid value!");
+        }
+        return parseBoolean(value);
+    }
+
+    private static Boolean parseBoolean(Object value) {
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        }
+        return value != null ? Boolean.valueOf(value.toString()) : null;
+    }
+
+    /**
+     * @return <code>true</code> if foreign customizers (that are not part of a DP) are allowed, <code>false</code> if all customizers should be provided by this or an earlier DP.
+     */
+    public boolean isAllowForeignCustomizers() {
+        Boolean result = m_allowForeignCustomizers;
+        if (result == null) {
+            String prop = getFrameworkProperty(KEY_ALLOW_FOREIGN_CUSTOMIZERS);
+            if (prop != null) {
+                result = Boolean.valueOf(prop);
+            }
+        }
+        return (result == null) ? DEFAULT_ALLOW_FOREIGN_CUSTOMIZERS : result.booleanValue();
+    }
+
+    /**
+     * @return <code>true</code> if all bundles should be stopped during the installation of a DP, <code>false</code> if only affected bundles should be stopped.
+     */
+    public boolean isStopUnaffectedBundles() {
+        Boolean result = m_stopUnaffectedBundles;
+        if (result == null) {
+            String prop = getFrameworkProperty(KEY_STOP_UNAFFECTED_BUNDLE);
+            if (prop != null) {
+                result = Boolean.valueOf(prop);
+            }
+        }
+        return (result == null) ? DEFAULT_STOP_UNAFFECTED_BUNDLE : result.booleanValue();
+    }
+
+    private String getFrameworkProperty(String key) {
+        String prop = m_context.getProperty(DeploymentAdminImpl.PID + "." + key);
+        if (prop == null) {
+            prop = m_context.getProperty(DeploymentAdminImpl.PID + "." + key.toLowerCase());
+        }
+        return prop;
+    }
+}
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
index 70ff456..2266d46 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
@@ -70,15 +70,13 @@
     public static final String TEMP_POSTFIX = "";
 
     private static final long TIMEOUT = 10000;
-    /** Configuration key used for dynamic configuration of DA. */
-    public static final String KEY_STOP_UNAFFECTED_BUNDLE = "stopUnaffectedBundle";
 
     private volatile BundleContext m_context;       /* will be injected by dependencymanager */
     private volatile PackageAdmin m_packageAdmin;   /* will be injected by dependencymanager */
     private volatile EventAdmin m_eventAdmin;       /* will be injected by dependencymanager */
     private volatile LogService m_log;              /* will be injected by dependencymanager */
-    private volatile DeploymentSessionImpl m_session = null;
-    private volatile Boolean m_stopUnaffectedBundles = null;
+    private volatile DeploymentSessionImpl m_session;
+    private volatile DeploymentAdminConfig m_config;
     
     private final Map m_packages = new HashMap();
     private final Semaphore m_semaphore = new Semaphore();
@@ -115,6 +113,13 @@
         return m_context;
     }
 
+    /**
+     * @return the configuration for this {@link DeploymentAdmin} instance, never <code>null</code>.
+     */
+    public DeploymentAdminConfig getConfiguration() {
+        return m_config;
+    }
+
     public DeploymentPackage getDeploymentPackage(Bundle bundle) {
         if (bundle == null) {
             throw new IllegalArgumentException("Bundle can not be null");
@@ -147,7 +152,7 @@
         return m_packageAdmin;
     }
 
-    public DeploymentPackage installDeploymentPackage(InputStream sourceInput) throws DeploymentException {
+	public DeploymentPackage installDeploymentPackage(InputStream sourceInput) throws DeploymentException {
         if (sourceInput == null) {
             throw new IllegalArgumentException("Inputstream may not be null");
         }
@@ -279,7 +284,7 @@
         }
     }
 
-	public DeploymentPackage[] listDeploymentPackages() {
+    public DeploymentPackage[] listDeploymentPackages() {
         Collection packages = m_packages.values();
         return (DeploymentPackage[]) packages.toArray(new DeploymentPackage[packages.size()]);
     }
@@ -288,6 +293,9 @@
      * Called by dependency manager upon start of this component.
      */
     public void start() throws DeploymentException {
+        // Create a default configuration...
+        m_config = new DeploymentAdminConfig(m_context);
+        
         File packageDir = m_context.getDataFile(PACKAGE_DIR);
         if (packageDir == null) {
             throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not create directories needed for deployment package persistence");
@@ -315,8 +323,10 @@
      */
     public void stop() {
     	cancel();
-    }
 
+    	m_config = null;
+    }
+    
     /**
      * Uninstalls the given deployment package from the system.
      * 
@@ -367,69 +377,7 @@
     }
     
     public void updated(Dictionary properties) throws ConfigurationException {
-        Boolean stopUnaffectedBundles = null;
-        if (properties != null) {
-            Object value = properties.get(KEY_STOP_UNAFFECTED_BUNDLE);
-            if (value == null || !(value instanceof String || value instanceof Boolean)) {
-                throw new ConfigurationException(KEY_STOP_UNAFFECTED_BUNDLE, "missing value!");
-            }
-
-            if (value instanceof Boolean) {
-                stopUnaffectedBundles = (Boolean) value;
-            } else {
-                stopUnaffectedBundles = Boolean.valueOf(value.toString());
-            }
-        }
-        m_stopUnaffectedBundles = stopUnaffectedBundles;
-    }
-    
-    public boolean isStopUnaffectedBundles() {
-        Boolean stopUnaffectedBundles = m_stopUnaffectedBundles;
-        if (stopUnaffectedBundles == null) {
-            String prop = m_context.getProperty(PID + "." + KEY_STOP_UNAFFECTED_BUNDLE);
-            if (prop == null) {
-                prop = m_context.getProperty(PID + "." + KEY_STOP_UNAFFECTED_BUNDLE.toLowerCase());
-            }
-            if (prop != null) {
-                stopUnaffectedBundles = Boolean.valueOf(prop);
-            }
-        }
-        return (stopUnaffectedBundles == null) ? true : stopUnaffectedBundles.booleanValue();
-    }
-    
-    private List createInstallCommandChain() {
-        List commandChain = new ArrayList();
-
-        GetStorageAreaCommand getStorageAreaCommand = new GetStorageAreaCommand();
-        commandChain.add(getStorageAreaCommand);
-        commandChain.add(new StopBundleCommand());
-        commandChain.add(new SnapshotCommand(getStorageAreaCommand));
-        commandChain.add(new UpdateCommand());
-        commandChain.add(new StartCustomizerCommand());
-        CommitResourceCommand commitCommand = new CommitResourceCommand();
-        commandChain.add(new ProcessResourceCommand(commitCommand));
-        commandChain.add(new DropResourceCommand(commitCommand));
-        commandChain.add(new DropBundleCommand());
-        commandChain.add(commitCommand);
-        commandChain.add(new StartBundleCommand());
-        
-        return commandChain;
-    }
-    
-    private List createUninstallCommandChain() {
-        List commandChain = new ArrayList();
-
-        GetStorageAreaCommand getStorageAreaCommand = new GetStorageAreaCommand();
-        commandChain.add(getStorageAreaCommand);
-        commandChain.add(new StopBundleCommand());
-        commandChain.add(new SnapshotCommand(getStorageAreaCommand));
-        commandChain.add(new StartCustomizerCommand());
-        CommitResourceCommand commitCommand = new CommitResourceCommand();
-        commandChain.add(new DropAllResourcesCommand(commitCommand));
-        commandChain.add(commitCommand);
-        commandChain.add(new DropAllBundlesCommand());
-        
-        return commandChain;
+        m_config = new DeploymentAdminConfig(m_context, properties);
     }
 
     /**
@@ -459,6 +407,41 @@
         return props;
     }
     
+    private List createInstallCommandChain() {
+        List commandChain = new ArrayList();
+
+        GetStorageAreaCommand getStorageAreaCommand = new GetStorageAreaCommand();
+        commandChain.add(getStorageAreaCommand);
+        commandChain.add(new StopBundleCommand());
+        commandChain.add(new SnapshotCommand(getStorageAreaCommand));
+        commandChain.add(new UpdateCommand());
+        commandChain.add(new StartCustomizerCommand());
+        CommitResourceCommand commitCommand = new CommitResourceCommand();
+        commandChain.add(new ProcessResourceCommand(commitCommand));
+        commandChain.add(new DropResourceCommand(commitCommand));
+        commandChain.add(new DropBundleCommand());
+        commandChain.add(commitCommand);
+        commandChain.add(new StartBundleCommand());
+        
+        return commandChain;
+    }
+
+    private List createUninstallCommandChain() {
+        List commandChain = new ArrayList();
+
+        GetStorageAreaCommand getStorageAreaCommand = new GetStorageAreaCommand();
+        commandChain.add(getStorageAreaCommand);
+        commandChain.add(new StopBundleCommand());
+        commandChain.add(new SnapshotCommand(getStorageAreaCommand));
+        commandChain.add(new StartCustomizerCommand());
+        CommitResourceCommand commitCommand = new CommitResourceCommand();
+        commandChain.add(new DropAllResourcesCommand(commitCommand));
+        commandChain.add(commitCommand);
+        commandChain.add(new DropAllBundlesCommand());
+        
+        return commandChain;
+    }
+    
     /**
      * Searches for a deployment package that contains a bundle with the given symbolic name.
      * 
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
index 425a7b1..f83bc86 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
@@ -25,6 +25,7 @@
 import java.util.ListIterator;
 
 import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.DeploymentAdminConfig;
 import org.apache.felix.deploymentadmin.DeploymentAdminImpl;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -43,15 +44,18 @@
     private final AbstractDeploymentPackage m_source;
     private final List m_commands;
     private final DeploymentAdminImpl m_admin;
+    private final DeploymentAdminConfig m_config;
 
     private volatile Command m_currentCommand = null;
     private volatile boolean m_cancelled;
 
-    public DeploymentSessionImpl(AbstractDeploymentPackage source, AbstractDeploymentPackage target, List commands, DeploymentAdminImpl admin) {
+    public DeploymentSessionImpl(AbstractDeploymentPackage source, AbstractDeploymentPackage target, List commands,
+        DeploymentAdminImpl admin) {
         m_source = source;
         m_target = target;
         m_commands = commands;
         m_admin = admin;
+        m_config = new DeploymentAdminConfig(m_admin.getConfiguration());
     }
 
     /**
@@ -91,13 +95,6 @@
         m_currentCommand = null;
     }
 
-    private void rollback(List executedCommands) {
-        for (ListIterator i = executedCommands.listIterator(executedCommands.size()); i.hasPrevious();) {
-            Command command = (Command) i.previous();
-            command.rollback(this);
-        }
-    }
-
     /**
      * Cancels the session if it is in progress.
      * 
@@ -117,6 +114,22 @@
     }
 
     /**
+     * Returns the bundle context of the bundle this class is part of.
+     * 
+     * @return The <code>BundleContext</code>.
+     */
+    public BundleContext getBundleContext() {
+        return m_admin.getBundleContext();
+    }
+
+    /**
+     * @return the configuration for this session, is guaranteed to remain stable during this session, never <code>null</code>.
+     */
+    public DeploymentAdminConfig getConfiguration() {
+        return m_config;
+    }
+
+    /**
      * Retrieve the base directory of the persistent storage area according to
      * OSGi Core R4 6.1.6.10 for the given <code>BundleContext</code>.
      * 
@@ -130,7 +143,8 @@
         BundleContext context = bundle.getBundleContext();
         if (context != null) {
             result = context.getDataFile("");
-        } else {
+        }
+        else {
             // TODO this method should not return null or throw an exception; we
             // need to resolve this...
             throw new IllegalStateException("Could not retrieve valid bundle context from bundle " + bundle.getSymbolicName());
@@ -142,23 +156,6 @@
         return result;
     }
 
-    public DeploymentPackage getSourceDeploymentPackage() {
-        return m_source;
-    }
-
-    public DeploymentPackage getTargetDeploymentPackage() {
-        return m_target;
-    }
-
-    /**
-     * Returns the bundle context of the bundle this class is part of.
-     * 
-     * @return The <code>BundleContext</code>.
-     */
-    public BundleContext getBundleContext() {
-        return m_admin.getBundleContext();
-    }
-
     /**
      * Returns the currently present log service.
      * 
@@ -178,6 +175,20 @@
     }
 
     /**
+     * Returns the source deployment package as an
+     * <code>AbstractDeploymentPackage</code>.
+     * 
+     * @return The source deployment packge of the session.
+     */
+    public AbstractDeploymentPackage getSourceAbstractDeploymentPackage() {
+        return m_source;
+    }
+
+    public DeploymentPackage getSourceDeploymentPackage() {
+        return m_source;
+    }
+
+    /**
      * Returns the target deployment package as an
      * <code>AbstractDeploymentPackage</code>.
      * 
@@ -187,17 +198,14 @@
         return m_target;
     }
 
-    /**
-     * Returns the source deployment package as an
-     * <code>AbstractDeploymentPackage</code>.
-     * 
-     * @return The source deployment packge of the session.
-     */
-    public AbstractDeploymentPackage getSourceAbstractDeploymentPackage() {
-        return m_source;
+    public DeploymentPackage getTargetDeploymentPackage() {
+        return m_target;
     }
-    
-    public boolean isStopUnaffectedBundles() {
-        return m_admin.isStopUnaffectedBundles();
+
+    private void rollback(List executedCommands) {
+        for (ListIterator i = executedCommands.listIterator(executedCommands.size()); i.hasPrevious();) {
+            Command command = (Command) i.previous();
+            command.rollback(this);
+        }
     }
 }
\ No newline at end of file
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java
index c4f4edf..cfa6d84 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java
@@ -72,8 +72,6 @@
         }
 
         try {
-            String allowForeignCustomerizers = System.getProperty("org.apache.felix.deploymentadmin.allowforeigncustomizers", "false");
-
             while (!expectedResources.isEmpty()) {
                 AbstractInfo jarEntry = source.getNextEntry();
                 if (jarEntry == null) {
@@ -88,39 +86,49 @@
                 }
 
                 ServiceReference ref = source.getResourceProcessor(name);
-                if (ref != null) {
-                    String serviceOwnerSymName = ref.getBundle().getSymbolicName();
-                    if (source.getBundleInfoByName(serviceOwnerSymName) != null || "true".equals(allowForeignCustomerizers)) {
-                        ResourceProcessor resourceProcessor = (ResourceProcessor) context.getService(ref);
-                        if (resourceProcessor != null) {
-                            try {
-                                if (m_commitCommand.addResourceProcessor(resourceProcessor)) {
-                                    resourceProcessor.begin(session);
-                                }
-                                resourceProcessor.process(name, source.getCurrentEntryStream());
-                            }
-                            catch (ResourceProcessorException rpe) {
-                                if (rpe.getCode() == ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION) {
-                                    throw new DeploymentException(DeploymentException.CODE_RESOURCE_SHARING_VIOLATION, "Sharing violation while processing resource '" + name + "'", rpe);
-                                } else {
-                                    throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Error while processing resource '" + name + "'", rpe);
-                                }
-                            }
-                        } else {
-                            throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "No resource processor for resource: '" + name + "'");
-                        }
-                    } else {
-                        throw new DeploymentException(DeploymentException.CODE_FOREIGN_CUSTOMIZER, "Resource processor for resource '" + name + "' belongs to foreign deployment package");
-                    }
-                } else {
+                if (ref == null) {
                     throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "No resource processor for resource: '" + name + "'");
                 }
+                if (!isValidCustomizer(session, ref)) {
+                    throw new DeploymentException(DeploymentException.CODE_FOREIGN_CUSTOMIZER, "Resource processor for resource '" + name + "' belongs to foreign deployment package");
+                }
+
+                ResourceProcessor resourceProcessor = (ResourceProcessor) context.getService(ref);
+                if (resourceProcessor == null) {
+                    throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "No resource processor for resource: '" + name + "'");
+                }
+
+                try {
+                    if (m_commitCommand.addResourceProcessor(resourceProcessor)) {
+                        resourceProcessor.begin(session);
+                    }
+                    resourceProcessor.process(name, source.getCurrentEntryStream());
+                }
+                catch (ResourceProcessorException rpe) {
+                    if (rpe.getCode() == ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION) {
+                        throw new DeploymentException(DeploymentException.CODE_RESOURCE_SHARING_VIOLATION, "Sharing violation while processing resource '" + name + "'", rpe);
+                    } else {
+                        throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Error while processing resource '" + name + "'", rpe);
+                    }
+                }
             }
         }
         catch (IOException e) {
             throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Problem while reading stream", e);
         }
     }
+    
+    private boolean isValidCustomizer(DeploymentSessionImpl session, ServiceReference ref) {
+        if (session.getConfiguration().isAllowForeignCustomizers()) {
+            // If foreign customizers are allowed, any non-null customizer will do...
+            return ref != null;
+        }
+
+        AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+        String serviceOwnerSymName = ref.getBundle().getSymbolicName();
+        // If only local customizers are allowed, we must be able to find this customizer in our DP...
+        return source.getBundleInfoByName(serviceOwnerSymName) != null;
+    }
 
     private class RollbackCommitAction extends AbstractAction {
         private final DeploymentSessionImpl m_session;
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
index 2d9d423..f7e4d03 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
@@ -85,7 +85,7 @@
      *         deployment package. Returns <code>false</code> otherwise.
      */
     private boolean omitBundleStop(DeploymentSessionImpl session, String symbolicName) {
-        boolean stopUnaffectedBundle = session.isStopUnaffectedBundles();
+        boolean stopUnaffectedBundle = session.getConfiguration().isStopUnaffectedBundles();
 
         boolean result = stopUnaffectedBundle;
         BundleInfoImpl sourceBundleInfo = session.getSourceAbstractDeploymentPackage().getBundleInfoByName(symbolicName);
diff --git a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminConfigTest.java b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminConfigTest.java
new file mode 100644
index 0000000..f53ff36
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminConfigTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.deploymentadmin;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * Test cases for {@link DeploymentAdminConfig}.
+ */
+public class DeploymentAdminConfigTest extends TestCase
+{
+    private static final String KEY_STOP_UNAFFECTED_BUNDLE = DeploymentAdminConfig.KEY_STOP_UNAFFECTED_BUNDLE;
+    private static final String KEY_ALLOW_FOREIGN_CUSTOMIZERS = DeploymentAdminConfig.KEY_ALLOW_FOREIGN_CUSTOMIZERS;
+    
+    private static final boolean DEFAULT_STOP_UNAFFECTED_BUNDLE = DeploymentAdminConfig.DEFAULT_STOP_UNAFFECTED_BUNDLE;
+    private static final boolean DEFAULT_ALLOW_FOREIGN_CUSTOMIZERS = DeploymentAdminConfig.DEFAULT_ALLOW_FOREIGN_CUSTOMIZERS;
+
+    private static final String SYS_PROP = DeploymentAdminImpl.PID + "." + KEY_STOP_UNAFFECTED_BUNDLE.toLowerCase();
+    private static final String SYS_PROP_LOWERCASE = DeploymentAdminImpl.PID + "." + KEY_STOP_UNAFFECTED_BUNDLE.toLowerCase();
+
+    private final Map m_fwProperties = new HashMap();
+
+    /**
+     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+     */
+    public void testDefaultConfigurationOk() throws ConfigurationException
+    {
+        DeploymentAdminConfig config = createDeploymentAdminConfig(null);
+
+        assertEquals(DEFAULT_STOP_UNAFFECTED_BUNDLE, config.isStopUnaffectedBundles());
+        assertEquals(DEFAULT_ALLOW_FOREIGN_CUSTOMIZERS, config.isAllowForeignCustomizers());
+    }
+
+    /**
+     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+     */
+    public void testExplicitConfigurationOk() throws ConfigurationException
+    {
+        Dictionary dict = new Hashtable();
+        dict.put(KEY_STOP_UNAFFECTED_BUNDLE, "false");
+        dict.put(KEY_ALLOW_FOREIGN_CUSTOMIZERS, "true");
+
+        DeploymentAdminConfig config = createDeploymentAdminConfig(dict);
+
+        // Should use the explicit configured value...
+        assertFalse(config.isStopUnaffectedBundles());
+        assertTrue(config.isAllowForeignCustomizers());
+    }
+
+    /**
+     * Tests that an explicit configuration cannot miss any properties. 
+     */
+    public void testExplicitConfigurationWithMissingValueFail() throws ConfigurationException
+    {
+        Dictionary dict = new Hashtable();
+        dict.put(KEY_ALLOW_FOREIGN_CUSTOMIZERS, "true");
+
+        try
+        {
+            createDeploymentAdminConfig(dict);
+            fail("ConfigurationException expected!");
+        }
+        catch (ConfigurationException e)
+        {
+            assertEquals(KEY_STOP_UNAFFECTED_BUNDLE, e.getProperty());
+        }
+        
+        dict = new Hashtable();
+        dict.put(KEY_STOP_UNAFFECTED_BUNDLE, "true");
+
+        try
+        {
+            createDeploymentAdminConfig(dict);
+            fail("ConfigurationException expected!");
+        }
+        catch (ConfigurationException e)
+        {
+            assertEquals(KEY_ALLOW_FOREIGN_CUSTOMIZERS, e.getProperty());
+        }
+    }
+
+    /**
+     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+     */
+    public void testFrameworkConfigurationOk() throws ConfigurationException
+    {
+        m_fwProperties.put(SYS_PROP, "false");
+
+        DeploymentAdminConfig config = createDeploymentAdminConfig(null);
+
+        assertEquals(false, config.isStopUnaffectedBundles());
+        assertEquals(DEFAULT_ALLOW_FOREIGN_CUSTOMIZERS, config.isAllowForeignCustomizers());
+    }
+
+    /**
+     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
+     */
+    public void testSystemConfigurationOk() throws ConfigurationException
+    {
+        System.setProperty(SYS_PROP, "false");
+
+        try
+        {
+            DeploymentAdminConfig config = createDeploymentAdminConfig(null);
+
+            assertFalse(config.isStopUnaffectedBundles());
+        }
+        finally
+        {
+            System.clearProperty(SYS_PROP);
+        }
+
+        System.setProperty(SYS_PROP_LOWERCASE, "false");
+
+        try
+        {
+            DeploymentAdminConfig config = createDeploymentAdminConfig(null);
+
+            assertFalse(config.isStopUnaffectedBundles());
+        }
+        finally
+        {
+            System.clearProperty(SYS_PROP_LOWERCASE);
+        }
+    }
+
+    protected void setUp() throws Exception
+    {
+        m_fwProperties.clear();
+    }
+
+    private DeploymentAdminConfig createDeploymentAdminConfig(Dictionary dict) throws ConfigurationException
+    {
+        return new DeploymentAdminConfig(createMockBundleContext(), dict);
+    }
+
+    private BundleContext createMockBundleContext()
+    {
+        BundleContext result = (BundleContext) Mockito.mock(BundleContext.class);
+        Mockito.when(result.getProperty(Matchers.anyString())).thenAnswer(new Answer()
+        {
+            public Object answer(InvocationOnMock invocation) throws Throwable
+            {
+                String prop = (String) invocation.getArguments()[0];
+
+                Object result = m_fwProperties.get(prop);
+                if (result == null)
+                {
+                    result = System.getProperty(prop);
+                }
+                return result;
+            }
+        });
+        return result;
+    }
+}
diff --git a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminImplTest.java b/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminImplTest.java
deleted file mode 100644
index 4c9c90b..0000000
--- a/deploymentadmin/deploymentadmin/src/test/java/org/apache/felix/deploymentadmin/DeploymentAdminImplTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.deploymentadmin;
-
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
-import junit.framework.TestCase;
-
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.cm.ConfigurationException;
-
-/**
- * Test cases for {@link DeploymentAdminImpl}.
- */
-public class DeploymentAdminImplTest extends TestCase
-{
-    private static final String CONF_KEY = DeploymentAdminImpl.KEY_STOP_UNAFFECTED_BUNDLE;
-    private static final String SYS_PROP = DeploymentAdminImpl.PID + "." + CONF_KEY.toLowerCase();
-    private static final String SYS_PROP_LOWERCASE = DeploymentAdminImpl.PID + "." + CONF_KEY.toLowerCase();
-
-    private final Map m_fwProperties = new HashMap();
-
-    /**
-     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
-     */
-    public void testDefaultConfigurationOk()
-    {
-        DeploymentAdminImpl da = createDeploymentAdmin();
-
-        assertTrue(da.isStopUnaffectedBundles());
-    }
-
-    /**
-     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
-     */
-    public void testExplicitConfigurationOk() throws ConfigurationException
-    {
-        Dictionary dict = new Hashtable();
-        dict.put(CONF_KEY, "false");
-
-        DeploymentAdminImpl da = createDeploymentAdmin();
-        da.updated(dict);
-
-        // Should use the explicit configured value...
-        assertFalse(da.isStopUnaffectedBundles());
-
-        da.updated(null);
-
-        // Should use the system wide value...
-        assertTrue(da.isStopUnaffectedBundles());
-    }
-
-    /**
-     * Tests that an explicit configuration cannot miss any properties. 
-     */
-    public void testExplicitConfigurationWithMissingValueFail() throws ConfigurationException
-    {
-        Dictionary dict = new Hashtable();
-
-        DeploymentAdminImpl da = createDeploymentAdmin();
-        try
-        {
-            da.updated(dict);
-            fail("ConfigurationException expected!");
-        }
-        catch (ConfigurationException e)
-        {
-            assertEquals(CONF_KEY, e.getProperty());
-        }
-    }
-
-    /**
-     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
-     */
-    public void testFrameworkConfigurationOk()
-    {
-        m_fwProperties.put(SYS_PROP, "false");
-
-        DeploymentAdminImpl da = createDeploymentAdmin();
-
-        assertFalse(da.isStopUnaffectedBundles());
-    }
-
-    /**
-     * Tests the configuration values of {@link DeploymentAdminImpl} without any explicit configuration.
-     */
-    public void testSystemConfigurationOk()
-    {
-        System.setProperty(SYS_PROP, "false");
-
-        try
-        {
-            DeploymentAdminImpl da = createDeploymentAdmin();
-
-            assertFalse(da.isStopUnaffectedBundles());
-        }
-        finally
-        {
-            System.clearProperty(SYS_PROP);
-        }
-
-        System.setProperty(SYS_PROP_LOWERCASE, "false");
-
-        try
-        {
-            DeploymentAdminImpl da = createDeploymentAdmin();
-
-            assertFalse(da.isStopUnaffectedBundles());
-        }
-        finally
-        {
-            System.clearProperty(SYS_PROP_LOWERCASE);
-        }
-    }
-
-    protected void setUp() throws Exception
-    {
-        m_fwProperties.clear();
-    }
-
-    private DeploymentAdminImpl createDeploymentAdmin()
-    {
-        return new DeploymentAdminImpl(createMockBundleContext());
-    }
-
-    private BundleContext createMockBundleContext()
-    {
-        BundleContext result = (BundleContext) Mockito.mock(BundleContext.class);
-        Mockito.when(result.getProperty(Matchers.anyString())).thenAnswer(new Answer()
-        {
-            public Object answer(InvocationOnMock invocation) throws Throwable
-            {
-                String prop = (String) invocation.getArguments()[0];
-
-                Object result = m_fwProperties.get(prop);
-                if (result == null)
-                {
-                    result = System.getProperty(prop);
-                }
-                return result;
-            }
-        });
-        return result;
-    }
-}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
index fa03aba..d50aad5 100644
--- a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
@@ -49,7 +49,8 @@
     {
         Dictionary props = new Hashtable();
         props.put("stopUnaffectedBundle", Boolean.FALSE);
-        
+        props.put("allowForeignCustomizers", Boolean.FALSE);
+
         Configuration config = m_configAdmin.getConfiguration("org.apache.felix.deploymentadmin", null);
         config.update(props);