Added a whole collection of tests to ensure that DeploymentAdmin conforms to the specification and works correctly. Refactored some of the code. Specifically modified the uninstall behavior to make it spec compliant.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1352090 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
index d8dbd33..808ce34 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
@@ -146,10 +146,14 @@
         if (isStale()) {
             throw new IllegalStateException("Can not get bundle from stale deployment package.");
         }
-        if (m_nameToBundleInfo.containsKey(symbolicName)) {
+        
+        BundleInfo bundleInfo = (BundleInfo) m_nameToBundleInfo.get(symbolicName);
+        if (bundleInfo != null) {
+            Version version = bundleInfo.getVersion();
+
             Bundle[] bundles = m_bundleContext.getBundles();
             for (int i = 0; i < bundles.length; i++) {
-                if (symbolicName.equals(bundles[i].getSymbolicName())) {
+                if (symbolicName.equals(bundles[i].getSymbolicName()) && version.equals(bundles[i].getVersion())) {
                     return bundles[i];
                 }
             }
@@ -272,6 +276,14 @@
         return m_isStale;
     }
     
+    /**
+     * @return <code>true</code> if this package is actually an empty package used for 
+     *         installing new deployment packages, <code>false</code> otherwise.
+     */
+    public boolean isNew() {
+        return this == EMPTY_PACKAGE;
+    }
+    
     public void setStale(boolean isStale) {
         m_isStale = isStale;
     }
@@ -280,12 +292,23 @@
         if (isStale()) {
             throw new IllegalStateException("Deployment package is stale, cannot uninstall.");
         }
-        m_deploymentAdmin.uninstallDeploymentPackage(this);
-        setStale(true);
+        try {
+            m_deploymentAdmin.uninstallDeploymentPackage(this, false /* force */);
+        } finally {
+            setStale(true);
+        }
     }
 
     public boolean uninstallForced() throws DeploymentException {
-        throw new IllegalStateException("Not implemented, use uninstall() for now.");
+        if (isStale()) {
+            throw new IllegalStateException("Deployment package is stale, cannot uninstall.");
+        }
+        try {
+            m_deploymentAdmin.uninstallDeploymentPackage(this, true /* force */);
+        } finally {
+            setStale(true);
+        }
+        return true;
     }
 
     /**
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java
index a2fcabe..0bdf653 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java
@@ -18,6 +18,8 @@
  */
 package org.apache.felix.deploymentadmin;
 
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
 public interface Constants extends org.osgi.framework.Constants {
 
     // manifest main attribute header constants
@@ -34,7 +36,11 @@
     public static final String EVENTTOPIC_INSTALL = "org/osgi/service/deployment/INSTALL";
     public static final String EVENTTOPIC_UNINSTALL = "org/osgi/service/deployment/UNINSTALL";
     public static final String EVENTTOPIC_COMPLETE = "org/osgi/service/deployment/COMPLETE";
-    public static final String EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME = "deploymentpackage.name";
+    
+    public static final String EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME = DeploymentPackage.EVENT_DEPLOYMENTPACKAGE_NAME;
+    public static final String EVENTPROPERTY_DEPLOYMENTPACKAGE_READABLENAME = DeploymentPackage.EVENT_DEPLOYMENTPACKAGE_READABLENAME;
+    public static final String EVENTPROPERTY_DEPLOYMENTPACKAGE_CURRENTVERSION = DeploymentPackage.EVENT_DEPLOYMENTPACKAGE_CURRENTVERSION;
+    public static final String EVENTPROPERTY_DEPLOYMENTPACKAGE_NEXTVERSION = DeploymentPackage.EVENT_DEPLOYMENTPACKAGE_NEXTVERSION;
     public static final String EVENTPROPERTY_SUCCESSFUL = "successful";
 
     // miscellaneous constants
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 89a6cf6..18d7f56 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
@@ -22,10 +22,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Dictionary;
 import java.util.HashMap;
-import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -34,6 +34,8 @@
 
 import org.apache.felix.deploymentadmin.spi.CommitResourceCommand;
 import org.apache.felix.deploymentadmin.spi.DeploymentSessionImpl;
+import org.apache.felix.deploymentadmin.spi.DropAllBundlesCommand;
+import org.apache.felix.deploymentadmin.spi.DropAllResourcesCommand;
 import org.apache.felix.deploymentadmin.spi.DropBundleCommand;
 import org.apache.felix.deploymentadmin.spi.DropResourceCommand;
 import org.apache.felix.deploymentadmin.spi.GetStorageAreaCommand;
@@ -45,6 +47,7 @@
 import org.apache.felix.deploymentadmin.spi.UpdateCommand;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
 import org.osgi.service.deploymentadmin.DeploymentAdmin;
 import org.osgi.service.deploymentadmin.DeploymentException;
 import org.osgi.service.deploymentadmin.DeploymentPackage;
@@ -70,54 +73,12 @@
     private volatile LogService m_log;              /* will be injected by dependencymanager */
     private volatile DeploymentSessionImpl m_session = null;
     private final Map m_packages = new HashMap();
-    private final List m_commandChain = new ArrayList();
     private final Semaphore m_semaphore = new Semaphore();
 
     /**
      * Create new instance of this <code>DeploymentAdmin</code>.
      */
     public DeploymentAdminImpl() {
-        GetStorageAreaCommand getStorageAreaCommand = new GetStorageAreaCommand();
-        m_commandChain.add(getStorageAreaCommand);
-        m_commandChain.add(new StopBundleCommand());
-        m_commandChain.add(new SnapshotCommand(getStorageAreaCommand));
-        m_commandChain.add(new UpdateCommand());
-        m_commandChain.add(new StartCustomizerCommand());
-        CommitResourceCommand commitCommand = new CommitResourceCommand();
-        m_commandChain.add(new ProcessResourceCommand(commitCommand));
-        m_commandChain.add(new DropResourceCommand(commitCommand));
-        m_commandChain.add(new DropBundleCommand());
-        m_commandChain.add(commitCommand);
-        m_commandChain.add(new StartBundleCommand());
-    }
-
-    // called automatically once dependencies are satisfied
-    public void start() throws DeploymentException {
-        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");
-        } else {
-            packageDir.mkdirs();
-            File[] packages = packageDir.listFiles();
-            for(int i = 0; i < packages.length; i++) {
-                if (packages[i].isDirectory()) {
-                    try {
-                        File index = new File(packages[i], PACKAGEINDEX_FILE);
-                        File contents = new File(packages[i], PACKAGECONTENTS_DIR);
-                        FileDeploymentPackage dp = new FileDeploymentPackage(index, contents, m_context, this);
-                        m_packages.put(dp.getName(), dp);
-                    }
-                    catch (IOException e) {
-                        m_log.log(LogService.LOG_WARNING, "Could not read deployment package from disk, skipping: '" + packages[i].getAbsolutePath() + "'");
-                        continue;
-                    }
-                }
-            }
-        }
-    }
-
-    public void stop() {
-    	cancel();
     }
 
     public boolean cancel() {
@@ -129,199 +90,6 @@
         return false;
     }
 
-    public DeploymentPackage getDeploymentPackage(String symbName) {
-        if (symbName == null) {
-            throw new IllegalArgumentException("Symbolic name may not be null");
-        }
-        return (DeploymentPackage) m_packages.get(symbName);
-    }
-
-    public DeploymentPackage getDeploymentPackage(Bundle bundle) {
-        if (bundle == null) {
-            throw new IllegalArgumentException("Bundle can not be null");
-        }
-        for (Iterator i = m_packages.values().iterator(); i.hasNext();) {
-            DeploymentPackage dp = (DeploymentPackage) i.next();
-            if (dp.getBundle(bundle.getSymbolicName()) != null) {
-                return dp;
-            }
-        }
-        return null;
-    }
-
-    public DeploymentPackage installDeploymentPackage(InputStream input) throws DeploymentException {
-        if (input == null) {
-            throw new IllegalArgumentException("Inputstream may not be null");
-        }
-        try {
-            if (!m_semaphore.tryAcquire(TIMEOUT)) {
-                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to install deployment package (" + TIMEOUT + " ms)");
-            }
-        }
-        catch (InterruptedException ie) {
-            throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Thread interrupted");
-        }
-
-        File tempPackage = null;
-        StreamDeploymentPackage source = null;
-        boolean succeeded = false;
-        try {
-            JarInputStream jarInput = null;
-            File tempIndex = null;
-            File tempContents = null;
-            try {
-                File tempDir = m_context.getDataFile(TEMP_DIR);
-                tempDir.mkdirs();
-                tempPackage = File.createTempFile(TEMP_PREFIX, TEMP_POSTFIX, tempDir);
-                tempPackage.delete();
-                tempPackage.mkdirs();
-                tempIndex = new File(tempPackage, PACKAGEINDEX_FILE);
-                tempContents = new File(tempPackage, PACKAGECONTENTS_DIR);
-                tempContents.mkdirs();
-                input = new ExplodingOutputtingInputStream(input, tempIndex, tempContents);
-            }
-            catch (IOException e) {
-                m_log.log(LogService.LOG_ERROR, "Error writing package to disk", e);
-                throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Error writing package to disk", e);
-            }
-            try {
-                jarInput = new JarInputStream(input);
-            }
-            catch (IOException e) {
-                m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid Jar", e);
-                throw new DeploymentException(DeploymentException.CODE_NOT_A_JAR, "Stream does not contain a valid Jar", e);
-            }
-            source = new StreamDeploymentPackage(jarInput, m_context, this);
-            sendStartedEvent(source.getName());
-            
-            AbstractDeploymentPackage target = (AbstractDeploymentPackage) getDeploymentPackage(source.getName());
-            boolean newPackage = (target == null);
-            if (newPackage) {
-                target = AbstractDeploymentPackage.EMPTY_PACKAGE;
-            }
-            if (source.isFixPackage() && ((newPackage) || (!source.getVersionRange().isInRange(target.getVersion())))) {
-                succeeded = false;
-                m_log.log(LogService.LOG_ERROR, "Target package version '" + target.getVersion() + "' is not in source range '" + source.getVersionRange() + "'");
-                throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Target package version '" + target.getVersion() + "' is not in source range '" + source.getVersionRange() + "'");
-            }
-            try {
-                m_session = new DeploymentSessionImpl(source, target, m_commandChain, this);
-                m_session.call();
-            }
-            catch (DeploymentException de) {
-                succeeded = false;
-                throw de;
-            } finally {
-                try {
-                    // make sure we've read until the end-of-stream, so the explodingoutput-wrapper can process all bytes
-                    Utils.readUntilEndOfStream(input);
-
-                    // note that calling close on this stream will wait until asynchronous processing is done
-                    input.close();
-                }
-                catch (IOException e) {
-                    m_log.log(LogService.LOG_ERROR, "Could not close stream properly", e);
-                    // Do not mask out any originally thrown exceptions...
-                    if (succeeded) {
-                        succeeded = false;
-                        throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not close stream properly", e);
-                    }
-                }
-            }
-
-            File targetContents = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName() + File.separator + PACKAGECONTENTS_DIR);
-            File targetIndex = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName() + File.separator + PACKAGEINDEX_FILE);
-            if (source.isFixPackage()) {
-                try {
-                    Utils.merge(targetIndex, targetContents, tempIndex, tempContents);
-                }
-                catch (IOException e) {
-                    succeeded = false;
-                    m_log.log(LogService.LOG_ERROR, "Could not merge source fix package with target deployment package", e);
-                    throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not merge source fix package with target deployment package", e);
-                }
-            }
-            else {
-                File targetPackage = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName());
-                targetPackage.mkdirs();
-                if (!Utils.replace(targetPackage, tempPackage)) {
-                	throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not replace " + targetPackage + " with " + tempPackage);
-                }
-            }
-            FileDeploymentPackage fileDeploymentPackage = null;
-            try {
-                fileDeploymentPackage = new FileDeploymentPackage(targetIndex, targetContents, m_context, this);
-                m_packages.put(source.getName(), fileDeploymentPackage);
-            }
-            catch (IOException e) {
-                succeeded = false;
-                m_log.log(LogService.LOG_ERROR, "Could not create installed deployment package from disk", e);
-                throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not create installed deployment package from disk", e);
-            }
-            succeeded = true;
-            return fileDeploymentPackage;
-        }
-        finally {
-            if (tempPackage != null) {
-                delete(tempPackage);
-            }
-        	if (source != null) {
-        	    sendCompleteEvent(source.getName(), succeeded);
-        	}
-            m_semaphore.release();
-        }
-    }
-    
-    public void uninstallDeploymentPackage(DeploymentPackage dp) throws DeploymentException {
-        try {
-            if (!m_semaphore.tryAcquire(TIMEOUT)) {
-                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to uninstall deployment package (" + TIMEOUT + " ms)");
-            }
-        }
-        catch (InterruptedException ie) {
-            throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Thread interrupted");
-        }
-        boolean succeeded = false;
-        AbstractDeploymentPackage source = AbstractDeploymentPackage.EMPTY_PACKAGE;
-        AbstractDeploymentPackage target = (AbstractDeploymentPackage) dp;
-        try {
-            try {
-                m_session = new DeploymentSessionImpl(source, target, m_commandChain, this);
-                m_session.call();
-            }
-            catch (DeploymentException de) {
-                succeeded = false;
-                throw de;
-            }
-
-            File targetPackage = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName());
-            delete(targetPackage);
-            m_packages.remove(dp.getName());
-            succeeded = true;
-        }
-        finally {
-            if (source != null) {
-                sendCompleteEvent(source.getName(), succeeded);
-            }
-            m_semaphore.release();
-        }
-    }
-
-    private void delete(File target) {
-    	if (target.isDirectory()) {
-            File[] childs = target.listFiles();
-            for (int i = 0; i < childs.length; i++) {
-                delete(childs[i]);
-            }
-        }
-    	target.delete();
-	}
-
-	public DeploymentPackage[] listDeploymentPackages() {
-        Collection packages = m_packages.values();
-        return (DeploymentPackage[]) packages.toArray(new DeploymentPackage[packages.size()]);
-    }
-
     /**
      * Returns reference to this bundle's <code>BundleContext</code>
      *
@@ -331,6 +99,20 @@
         return m_context;
     }
 
+    public DeploymentPackage getDeploymentPackage(Bundle bundle) {
+        if (bundle == null) {
+            throw new IllegalArgumentException("Bundle can not be null");
+        }
+        return getDeploymentPackageContainingBundleWithSymbolicName(bundle.getSymbolicName());
+    }
+
+    public DeploymentPackage getDeploymentPackage(String symbName) {
+        if (symbName == null) {
+            throw new IllegalArgumentException("Symbolic name may not be null");
+        }
+        return (DeploymentPackage) m_packages.get(symbName);
+    }
+
     /**
      * Returns reference to the current logging service defined in the framework.
      *
@@ -349,19 +131,480 @@
         return m_packageAdmin;
     }
 
-    private void sendStartedEvent(String name) {
+    public DeploymentPackage installDeploymentPackage(InputStream input) throws DeploymentException {
+        if (input == null) {
+            throw new IllegalArgumentException("Inputstream may not be null");
+        }
+
+        try {
+            if (!m_semaphore.tryAcquire(TIMEOUT)) {
+                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to install deployment package (" + TIMEOUT + " ms)");
+            }
+        }
+        catch (InterruptedException ie) {
+            throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Thread interrupted");
+        }
+
+        File tempPackage = null;
+        StreamDeploymentPackage source = null;
+        AbstractDeploymentPackage target = null;
+        boolean succeeded = false;
+
+        try {
+            JarInputStream jarInput = null;
+            File tempIndex = null;
+            File tempContents = null;
+            try {
+                File tempDir = m_context.getDataFile(TEMP_DIR);
+                tempDir.mkdirs();
+                tempPackage = File.createTempFile(TEMP_PREFIX, TEMP_POSTFIX, tempDir);
+                tempPackage.delete();
+                tempPackage.mkdirs();
+                tempIndex = new File(tempPackage, PACKAGEINDEX_FILE);
+                tempContents = new File(tempPackage, PACKAGECONTENTS_DIR);
+                tempContents.mkdirs();
+                input = new ExplodingOutputtingInputStream(input, tempIndex, tempContents);
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Error writing package to disk", e);
+                throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Error writing package to disk", e);
+            }
+
+            try {
+                jarInput = new JarInputStream(input);
+                
+                if (jarInput.getManifest() == null) {
+                    m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid deployment package: missing manifest!");
+                    throw new DeploymentException(DeploymentException.CODE_MISSING_HEADER, "No manifest present in deployment package!");
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid Jar", e);
+                throw new DeploymentException(DeploymentException.CODE_NOT_A_JAR, "Stream does not contain a valid Jar", e);
+            }
+
+            source = new StreamDeploymentPackage(jarInput, m_context, this);
+            String dpSymbolicName = source.getName();
+            
+            target = getExistingOrEmptyDeploymentPackage(dpSymbolicName);
+            
+            // Fire an event that we're about to install a new package 
+            sendStartedEvent(source, target);
+
+            // Assert that:
+            //   the source has no bundles that exists in other packages than the target.
+            verifyNoResourcesShared(source, target);
+            
+            if (source.isFixPackage()) {
+                // Assert that:
+                //   a. the version of the target matches the required fix-package range;
+                //   b. all missing source bundles are present in the target.
+                verifyFixPackage(source, target);
+            } else {
+                // Assert that:
+                //   no missing resources or bundles are declared.
+                verifySourcePackage(source);
+            }
+
+            // To keep track whether or not we're masking an exception during the close of the input stream...
+            boolean installFailed = false;
+            
+            try {
+                m_session = new DeploymentSessionImpl(source, target, createInstallCommandChain(), this);
+                m_session.call(false /* ignoreExceptions */);
+            }
+            catch (DeploymentException de) {
+                installFailed = true;
+                throw de;
+            } finally {
+                try {
+                    // make sure we've read until the end-of-stream, so the explodingoutput-wrapper can process all bytes
+                    Utils.readUntilEndOfStream(input);
+
+                    // note that calling close on this stream will wait until asynchronous processing is done
+                    input.close();
+                }
+                catch (IOException e) {
+                    m_log.log(LogService.LOG_ERROR, "Could not close stream properly", e);
+                    // Do not mask out any originally thrown exceptions...
+                    if (!installFailed) {
+                        throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not close stream properly", e);
+                    }
+                }
+            }
+
+            String dpInstallBaseDirectory = PACKAGE_DIR + File.separator + dpSymbolicName;
+
+            File targetContents = m_context.getDataFile(dpInstallBaseDirectory + File.separator + PACKAGECONTENTS_DIR);
+            File targetIndex = m_context.getDataFile(dpInstallBaseDirectory + File.separator + PACKAGEINDEX_FILE);
+
+            if (source.isFixPackage()) {
+                try {
+                    Utils.merge(targetIndex, targetContents, tempIndex, tempContents);
+                }
+                catch (IOException e) {
+                    m_log.log(LogService.LOG_ERROR, "Could not merge source fix package with target deployment package", e);
+                    throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not merge source fix package with target deployment package", e);
+                }
+            }
+            else {
+                File targetPackage = m_context.getDataFile(dpInstallBaseDirectory);
+                targetPackage.mkdirs();
+                if (!Utils.replace(targetPackage, tempPackage)) {
+                	throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not replace " + targetPackage + " with " + tempPackage);
+                }
+            }
+            
+            FileDeploymentPackage fileDeploymentPackage = null;
+            try {
+                fileDeploymentPackage = new FileDeploymentPackage(targetIndex, targetContents, m_context, this);
+                m_packages.put(dpSymbolicName, fileDeploymentPackage);
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Could not create installed deployment package from disk", e);
+                throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not create installed deployment package from disk", e);
+            }
+
+            // Since we're here, it means everything went OK, so we might as well raise our success flag...
+            succeeded = true;
+
+            return fileDeploymentPackage;
+        }
+        finally {
+            if (tempPackage != null) {
+                Utils.delete(tempPackage);
+            }
+        	if (source != null) {
+        	    sendCompleteEvent(source, target, succeeded);
+        	}
+            m_semaphore.release();
+        }
+    }
+
+	public DeploymentPackage[] listDeploymentPackages() {
+        Collection packages = m_packages.values();
+        return (DeploymentPackage[]) packages.toArray(new DeploymentPackage[packages.size()]);
+    }
+
+    /**
+     * Called by dependency manager upon start of this component.
+     */
+    public void start() throws DeploymentException {
+        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");
+        } else {
+            packageDir.mkdirs();
+            File[] packages = packageDir.listFiles();
+            for(int i = 0; i < packages.length; i++) {
+                if (packages[i].isDirectory()) {
+                    try {
+                        File index = new File(packages[i], PACKAGEINDEX_FILE);
+                        File contents = new File(packages[i], PACKAGECONTENTS_DIR);
+                        FileDeploymentPackage dp = new FileDeploymentPackage(index, contents, m_context, this);
+                        m_packages.put(dp.getName(), dp);
+                    }
+                    catch (IOException e) {
+                        m_log.log(LogService.LOG_WARNING, "Could not read deployment package from disk, skipping: '" + packages[i].getAbsolutePath() + "'");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by dependency manager when stopping this component.
+     */
+    public void stop() {
+    	cancel();
+    }
+
+    /**
+     * Uninstalls the given deployment package from the system.
+     * 
+     * @param dp the deployment package to uninstall, cannot be <code>null</code>;
+     * @param forced <code>true</code> to force the uninstall, meaning that any exceptions are ignored during the uninstallation.
+     * @throws DeploymentException in case the uninstall failed.
+     */
+    public void uninstallDeploymentPackage(DeploymentPackage dp, boolean forced) throws DeploymentException {
+        try {
+            if (!m_semaphore.tryAcquire(TIMEOUT)) {
+                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to uninstall deployment package (" + TIMEOUT + " ms)");
+            }
+        }
+        catch (InterruptedException ie) {
+            throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Thread interrupted");
+        }
+        
+        boolean succeeded = false;
+        AbstractDeploymentPackage source = AbstractDeploymentPackage.EMPTY_PACKAGE;
+        AbstractDeploymentPackage target = (AbstractDeploymentPackage) dp;
+
+        // Notify listeners that we've about to uninstall the deployment package...
+        sendUninstallEvent(source, target);
+
+        try {
+            try {
+                m_session = new DeploymentSessionImpl(source, target, createUninstallCommandChain(), this);
+                m_session.call(forced /* ignoreExceptions */);
+            }
+            catch (DeploymentException de) {
+                throw de;
+            }
+
+            File targetPackage = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName());
+            Utils.delete(targetPackage);
+            
+            m_packages.remove(dp.getName());
+
+            succeeded = true;
+        }
+        finally {
+            if (source != null) {
+                sendCompleteEvent(source, target, succeeded);
+            }
+            m_semaphore.release();
+        }
+    }
+    
+    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;
+    }
+
+    /**
+     * Creates the properties for a new event.
+     * 
+     * @param source the source package being installed;
+     * @param target the current installed package (can be new).
+     * @return the event properties, never <code>null</code>.
+     */
+    private Dictionary createEventProperties(AbstractDeploymentPackage source, AbstractDeploymentPackage target) {
         Dictionary props = new Properties();
-        props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME, name);
-        Event completeEvent = new Event(Constants.EVENTTOPIC_INSTALL, props);
-        m_eventAdmin.postEvent(completeEvent);
+        if (source != null) {
+            String displayName = source.getDisplayName();
+            if (displayName == null) {
+                displayName = source.getName();
+            }
+
+            props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME, source.getName());
+            props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_READABLENAME, displayName);
+            if (!source.isNew()) {
+                props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_NEXTVERSION, source.getVersion());
+            }
+        }
+        if ((target != null) && !target.isNew()) {
+            props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_CURRENTVERSION, target.getVersion());
+        }
+        return props;
+    }
+    
+    /**
+     * Searches for a deployment package that contains a bundle with the given symbolic name.
+     * 
+     * @param symbolicName the symbolic name of the <em>bundle</em> to return the containing deployment package for, cannot be <code>null</code>.
+     * @return the deployment package containing the given bundle, or <code>null</code> if no deployment package contained such bundle.
+     */
+    private DeploymentPackage getDeploymentPackageContainingBundleWithSymbolicName(String symbolicName) {
+        for (Iterator i = m_packages.values().iterator(); i.hasNext();) {
+            DeploymentPackage dp = (DeploymentPackage) i.next();
+            if (dp.getBundle(symbolicName) != null) {
+                return dp;
+            }
+        }
+        return null;
     }
 
-    private void sendCompleteEvent(String name, boolean success) {
-        Dictionary props = new Hashtable();
-        props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME, name);
-        props.put(Constants.EVENTPROPERTY_SUCCESSFUL, new Boolean(success));
-        Event completeEvent = new Event(Constants.EVENTTOPIC_COMPLETE, props);
-        m_eventAdmin.postEvent(completeEvent);
+    /**
+     * Returns either an existing deployment package, or if no such package exists, an empty package.
+     * 
+     * @param symbolicName the name of the deployment package to retrieve, cannot be <code>null</code>.
+     * @return a deployment package, never <code>null</code>.
+     */
+    private AbstractDeploymentPackage getExistingOrEmptyDeploymentPackage(String symbolicName) {
+        AbstractDeploymentPackage result = (AbstractDeploymentPackage) getDeploymentPackage(symbolicName);
+        if (result == null) {
+            result = AbstractDeploymentPackage.EMPTY_PACKAGE;
+        }
+        return result;
     }
 
+    /**
+     * Returns all bundles that are not present in any deployment package. Ultimately, this should only 
+     * be one bundle, the system bundle, but this is not enforced in any way by the specification.
+     * 
+     * @return an array of non-deployment packaged bundles, never <code>null</code>.
+     */
+    private Bundle[] getNonDeploymentPackagedBundles() {
+        List result = new ArrayList(Arrays.asList(m_context.getBundles()));
+        
+        Iterator iter = result.iterator();
+        while (iter.hasNext()) {
+            Bundle suspect = (Bundle) iter.next();
+            if (suspect.getLocation().startsWith(Constants.BUNDLE_LOCATION_PREFIX)) {
+                iter.remove();
+            }
+        }
+
+        return (Bundle[]) result.toArray(new Bundle[result.size()]);
+    }
+
+    /**
+     * Sends out an event that the {@link #installDeploymentPackage(InputStream)} is 
+     * completed its installation of a deployment package.
+     * 
+     * @param source the source package being installed;
+     * @param target the current installed package (can be new);
+     * @param success <code>true</code> if the installation was successful, <code>false</code> otherwise.
+     */
+    private void sendCompleteEvent(AbstractDeploymentPackage source, AbstractDeploymentPackage target, boolean success) {
+        Dictionary props = createEventProperties(source, target);
+        props.put(Constants.EVENTPROPERTY_SUCCESSFUL, Boolean.valueOf(success));
+        
+        m_eventAdmin.postEvent(new Event(Constants.EVENTTOPIC_COMPLETE, props));
+    }
+
+    /**
+     * Sends out an event that the {@link #installDeploymentPackage(InputStream)} is about 
+     * to install a new deployment package.
+     * 
+     * @param source the source package being installed;
+     * @param target the current installed package (can be new).
+     */
+    private void sendStartedEvent(AbstractDeploymentPackage source, AbstractDeploymentPackage target) {
+        Dictionary props = createEventProperties(source, target);
+
+        m_eventAdmin.postEvent(new Event(Constants.EVENTTOPIC_INSTALL, props));
+    }
+
+    /**
+     * Sends out an event that the {@link #uninstallDeploymentPackage(DeploymentPackage)} is about 
+     * to uninstall a deployment package.
+     * 
+     * @param source the source package being uninstalled;
+     * @param target the current installed package (can be new).
+     */
+    private void sendUninstallEvent(AbstractDeploymentPackage source, AbstractDeploymentPackage target) {
+        Dictionary props = createEventProperties(source, target);
+
+        m_eventAdmin.postEvent(new Event(Constants.EVENTTOPIC_UNINSTALL, props));
+    }
+
+    /**
+     * Verifies that the version of the target matches the required source version range, and 
+     * whether all missing source resources are available in the target.
+     * 
+     * @param source the fix-package source to verify;
+     * @param target the target package to verify against.
+     * @throws DeploymentException in case verification failed.
+     */
+    private void verifyFixPackage(AbstractDeploymentPackage source, AbstractDeploymentPackage target) throws DeploymentException {
+        boolean newPackage = target.isNew();
+
+        // Verify whether the target package exists, and if so, falls in the requested fix-package range...
+        if (newPackage || (!source.getVersionRange().isInRange(target.getVersion()))) {
+            m_log.log(LogService.LOG_ERROR, "Target package version '" + target.getVersion() + "' is not in source range '" + source.getVersionRange() + "'");
+            throw new DeploymentException(DeploymentException.CODE_MISSING_FIXPACK_TARGET, "Target package version '" + target.getVersion() + "' is not in source range '" + source.getVersionRange() + "'");
+        }
+
+        // Verify whether all missing bundles are available in the target package...
+        BundleInfoImpl[] bundleInfos = source.getBundleInfoImpls();
+        for (int i = 0; i < bundleInfos.length; i++) {
+            if (bundleInfos[i].isMissing()) {
+                // Check whether the bundle exists in the target package...
+                BundleInfoImpl targetBundleInfo = target.getBundleInfoByPath(bundleInfos[i].getPath());
+                if (targetBundleInfo == null) {
+                    m_log.log(LogService.LOG_ERROR, "Missing bundle '" + bundleInfos[i].getSymbolicName() + "/" + bundleInfos[i].getVersion() + " does not exist in target package!");
+                    throw new DeploymentException(DeploymentException.CODE_MISSING_BUNDLE, "Missing bundle '" + bundleInfos[i].getSymbolicName() + "/" + bundleInfos[i].getVersion() + " does not exist in target package!");
+                }
+            }
+        }
+
+        // Verify whether all missing resources are available in the target package...
+        ResourceInfoImpl[] resourceInfos = source.getResourceInfos();
+        for (int i = 0; i < resourceInfos.length; i++) {
+            if (resourceInfos[i].isMissing()) {
+                // Check whether the resource exists in the target package...
+                ResourceInfoImpl targetResourceInfo = target.getResourceInfoByPath(resourceInfos[i].getPath());
+                if (targetResourceInfo == null) {
+                    m_log.log(LogService.LOG_ERROR, "Missing resource '" + resourceInfos[i].getPath() + " does not exist in target package!");
+                    throw new DeploymentException(DeploymentException.CODE_MISSING_RESOURCE, "Missing resource '" + resourceInfos[i].getPath() + " does not exist in target package!");
+                }
+            }
+        }
+    }
+
+    /**
+     * Verifies whether none of the mentioned resources in the source package are present in 
+     * deployment packages other than the given target.
+     * 
+     * @param source the source package to verify;
+     * @param target the target package to verify against.
+     * @throws DeploymentException in case verification fails.
+     */
+    private void verifyNoResourcesShared(AbstractDeploymentPackage source, AbstractDeploymentPackage target) throws DeploymentException {
+        Bundle[] foreignBundles = getNonDeploymentPackagedBundles();
+        
+        // Verify whether all source bundles are available in the target package or absent...
+        BundleInfoImpl[] bundleInfos = source.getBundleInfoImpls();
+        for (int i = 0; i < bundleInfos.length; i++) {
+            String symbolicName = bundleInfos[i].getSymbolicName();
+            Version version = bundleInfos[i].getVersion();
+
+            DeploymentPackage targetPackage = getDeploymentPackageContainingBundleWithSymbolicName(symbolicName);
+            // If found, it should match the given target DP; not found is also ok...
+            if ((targetPackage != null) && !targetPackage.equals(target)) {
+                m_log.log(LogService.LOG_ERROR, "Bundle '" + symbolicName + "/" + version + " already present in other deployment packages!");
+                throw new DeploymentException(DeploymentException.CODE_BUNDLE_SHARING_VIOLATION, "Bundle '" + symbolicName + "/" + version + " already present in other deployment packages!");
+            }
+            
+            if (targetPackage == null) {
+                // Maybe the bundle is installed without deployment admin...
+                for (int j = 0; j < foreignBundles.length; j++) {
+                    if (symbolicName.equals(foreignBundles[j].getSymbolicName()) && version.equals(foreignBundles[j].getVersion())) {
+                        m_log.log(LogService.LOG_ERROR, "Bundle '" + symbolicName + "/" + version + " already present!");
+                        throw new DeploymentException(DeploymentException.CODE_BUNDLE_SHARING_VIOLATION, "Bundle '" + symbolicName + "/" + version + " already present!");
+                    }
+                }
+            }
+        }
+        
+        // TODO verify other resources as well...
+    }
+
+    private void verifySourcePackage(AbstractDeploymentPackage source) throws DeploymentException {
+        // TODO this method should do a X-ref check between DP-manifest and JAR-entries...
+//        m_log.log(LogService.LOG_ERROR, "Missing bundle '" + symbolicName + "/" + bundleInfos[i].getVersion() + " does not exist in target package!");
+//        throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Missing bundle '" + symbolicName + "/" + bundleInfos[i].getVersion() + " is not part of target package!");
+    }
 }
\ No newline at end of file
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java
index 3983615..fd02419 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Utils.java
@@ -106,6 +106,18 @@
         return result;
     }
 
+
+    public static void delete(File target) {
+        // TODO merge with #delete(File, boolean)?!
+        if (target.isDirectory()) {
+            File[] childs = target.listFiles();
+            for (int i = 0; i < childs.length; i++) {
+                delete(childs[i]);
+            }
+        }
+        target.delete();
+    }
+
     public static boolean rename(File from, File to) {
         if (!from.renameTo(to)) {
             if (copy(from, to)) {
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java
index ec74568..e86edba 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java
@@ -43,6 +43,14 @@
             }
             catch (ResourceProcessorException e) {
                 session.getLog().log(LogService.LOG_ERROR, "Preparing commit for resource processor failed", e);
+                // Check what error code we got...
+                if (e.getCode() == ResourceProcessorException.CODE_PREPARE) {
+                    throw new DeploymentException(DeploymentException.CODE_COMMIT_ERROR, "Preparing commit for resource processor failed!", e);
+                }
+                throw new DeploymentException(e.getCode(), "Preparing commit for resource processor failed!", e);
+            }
+            catch (Exception e) {
+                session.getLog().log(LogService.LOG_ERROR, "Preparing commit for resource processor failed", e);
                 throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Preparing commit for resource processor failed", e);
             }
         }
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 b435666..f546370 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
@@ -19,7 +19,6 @@
 package org.apache.felix.deploymentadmin.spi;
 
 import java.io.File;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -60,7 +59,7 @@
      *
      * @throws DeploymentException If the session was canceled (<code>DeploymentException.CODE_CANCELLED</code>) or if one of the commands caused an exception (<code>DeploymentException.*</code>)
      */
-    public void call() throws DeploymentException {
+    public void call(boolean ignoreExceptions) throws DeploymentException {
         List executedCommands = new ArrayList();
         for (Iterator i = m_commands.iterator(); i.hasNext();) {
             if (m_cancelled) {
@@ -74,8 +73,10 @@
                 m_currentCommand.execute(this);
             }
             catch (DeploymentException de) {
-                rollback(executedCommands);
-                throw de;
+                if (!ignoreExceptions) {
+                    rollback(executedCommands);
+                    throw de;
+                }
             }
         }
         for (Iterator i = m_commands.iterator(); i.hasNext();) {
@@ -117,34 +118,17 @@
      *     persistent storage area for the bundle
      */
     public File getDataFile(Bundle bundle) {
-        BundleContext context = null;
-        try {
-            // try to find the method in the current class
-            Method getBundleContext = bundle.getClass().getDeclaredMethod("getBundleContext", null);
-            getBundleContext.setAccessible(true);
-            context = (BundleContext) getBundleContext.invoke(bundle, null);
-        }
-        catch (Exception e) {
-            // if we cannot find the method at first, we try again below
-        }
-        if (context == null) {
-            try {
-                // try to find the method in superclasses
-                Method getBundleContext = bundle.getClass().getMethod("getBundleContext", null);
-                getBundleContext.setAccessible(true);
-                context = (BundleContext) getBundleContext.invoke(bundle, null);
-            }
-            catch (Exception e) {
-                // we still can't find the method, we will throw an exception indicating that below
-            }
-        }
         File result = null;
+
+        BundleContext context = bundle.getBundleContext();
         if (context != null) {
             result = context.getDataFile("");
         }
         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());
         }
+
         if (result == null) {
             throw new IllegalStateException("Could not retrieve base directory for bundle " + bundle.getSymbolicName());
         }
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropAllBundlesCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropAllBundlesCommand.java
new file mode 100644
index 0000000..6a59e33
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropAllBundlesCommand.java
@@ -0,0 +1,93 @@
+/*
+ * 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.spi;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.BundleInfoImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that uninstalls all bundles, if rolled back the bundles are restored.
+ */
+public class DropAllBundlesCommand extends Command {
+
+    public void execute(DeploymentSessionImpl session) throws DeploymentException {
+        AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+        LogService log = session.getLog();
+
+        BundleInfoImpl[] orderedTargetBundles = target.getOrderedBundleInfos();
+        for (int i = orderedTargetBundles.length - 1; i >= 0; i--) {
+            BundleInfoImpl bundleInfo = orderedTargetBundles[i];
+            // stale bundle, save a copy for rolling back and uninstall it
+            String symbolicName = bundleInfo.getSymbolicName();
+            try {
+                Bundle bundle = target.getBundle(symbolicName);
+                if (bundle != null) {
+                    bundle.uninstall();
+                    addRollback(new InstallBundleRunnable(bundle, target, log));
+                }
+            }
+            catch (BundleException be) {
+                log.log(LogService.LOG_WARNING, "Bundle '" + symbolicName + "' could not be uninstalled", be);
+            }
+        }
+    }
+
+    private static class InstallBundleRunnable implements Runnable {
+
+        private final AbstractDeploymentPackage m_target;
+        private final Bundle m_bundle;
+        private final LogService m_log;
+
+        public InstallBundleRunnable(Bundle bundle, AbstractDeploymentPackage target, LogService log) {
+            m_bundle = bundle;
+            m_target = target;
+            m_log = log;
+        }
+
+        public void run() {
+            InputStream is = null;
+            try {
+                is = m_target.getBundleStream(m_bundle.getSymbolicName());
+                if (is != null) {
+                    m_bundle.update(is);
+                }
+                throw new Exception("Unable to get Inputstream for bundle " + m_bundle.getSymbolicName());
+            }
+            catch (Exception e) {
+                m_log.log(LogService.LOG_WARNING,
+                    "Could not rollback uninstallation of bundle '" + m_bundle.getSymbolicName() + "'", e);
+            }
+            finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    }
+                    catch (IOException e) {}
+                }
+            }
+        }
+    }
+}
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropAllResourcesCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropAllResourcesCommand.java
new file mode 100644
index 0000000..9b4a051
--- /dev/null
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropAllResourcesCommand.java
@@ -0,0 +1,112 @@
+/*
+ * 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.spi;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.ResourceInfoImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that drops resources.
+ */
+public class DropAllResourcesCommand extends Command {
+
+    private final CommitResourceCommand m_commitCommand;
+
+    /**
+     * Creates an instance of this command. The commit command is used to make sure
+     * the resource processors used to drop all resources will be committed at a later stage in the process.
+     *
+     * @param commitCommand The commit command that will be executed at a later stage in the process.
+     */
+    public DropAllResourcesCommand(CommitResourceCommand commitCommand) {
+        m_commitCommand = commitCommand;
+    }
+
+    public void execute(DeploymentSessionImpl session) throws DeploymentException {
+        // Allow proper rollback in case the drop fails...
+        addRollback(new RollbackCommitAction(session));
+        
+        AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+        BundleContext context = session.getBundleContext();
+        LogService log = session.getLog();
+
+        // Collect all unique paths of all processed resources...
+        Set resourceProcessors = new HashSet();
+
+        ResourceInfoImpl[] orderedTargetResources = target.getOrderedResourceInfos();
+        for (int i = orderedTargetResources.length - 1; i >= 0; i--) {
+            ResourceInfoImpl resourceInfo = orderedTargetResources[i];
+            
+            String rpName = resourceInfo.getResourceProcessor();
+            String path = resourceInfo.getPath();
+
+            if (resourceProcessors.contains(rpName)) {
+                // Already seen this RP; continue on the next one...
+                continue;
+            }
+
+            // Keep track of which resource processors we've seen already...
+            resourceProcessors.add(rpName);
+
+            ServiceReference ref = target.getResourceProcessor(path);
+            if (ref == null) {
+                log.log(LogService.LOG_ERROR, "Failed to find resource processor for '" + rpName + "'!");
+                throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "Failed to find resource processor '" + rpName + "'!");
+            }
+            
+            ResourceProcessor resourceProcessor = (ResourceProcessor) context.getService(ref);
+            if (resourceProcessor == null) {
+                log.log(LogService.LOG_ERROR, "Failed to find resource processor for '" + rpName + "'!");
+                throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "Failed to find resource processor '" + rpName + "'!");
+            }
+            
+            try {
+                if (m_commitCommand.addResourceProcessor(resourceProcessor)) {
+                    resourceProcessor.begin(session);
+                }
+                resourceProcessor.dropAllResources();
+            }
+            catch (ResourceProcessorException e) {
+                log.log(LogService.LOG_ERROR, "Failed to drop all resources for resource processor '" + rpName + "'!", e);
+                throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Failed to drop all resources for resource processor '" + rpName + "'!", e);
+            }
+        }
+    }
+    
+    private class RollbackCommitAction implements Runnable {
+        private final DeploymentSessionImpl m_session; 
+        
+        public RollbackCommitAction(DeploymentSessionImpl session) {
+            m_session = session;
+        }
+        
+        public void run() {
+            m_commitCommand.rollback(m_session);
+        }
+    }
+}
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 eb34fc0..29d85fb 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
@@ -65,7 +65,7 @@
         AbstractInfo[] resourceInfos = (AbstractInfo[]) source.getResourceInfos();
         for (int i = 0; i < resourceInfos.length; i++) {
             AbstractInfo resourceInfo = resourceInfos[i];
-            if(!resourceInfo.isMissing()) {
+            if (!resourceInfo.isMissing()) {
                 expectedResources.put(resourceInfo.getPath(), resourceInfo);
             }
         }
@@ -100,7 +100,7 @@
                             }
                             catch (ResourceProcessorException rpe) {
                                 if (rpe.getCode() == ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION) {
-                                    throw new DeploymentException(DeploymentException.CODE_RESOURCE_SHARING_VIOLATION, "Violation while processing resource '" + name + "'", rpe);
+                                    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);
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java
index 84133f3..a5a08a5 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java
@@ -91,8 +91,11 @@
                     }
                     throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not install new bundle '" + name + "'", be);
                 }
-                if (!bundle.getSymbolicName().equals(bundleInfo.getSymbolicName()) || !Version.parseVersion((String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION)).equals(bundleInfo.getVersion())) {
-                    throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Installed/updated bundle version and/or symbolicnames do not match what was installed/updated");
+                if (!bundle.getSymbolicName().equals(bundleInfo.getSymbolicName())) {
+                    throw new DeploymentException(DeploymentException.CODE_BUNDLE_NAME_ERROR, "Installed/updated bundle symbolicname do not match what was installed/updated");
+                }
+                if (!Version.parseVersion((String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION)).equals(bundleInfo.getVersion())) {
+                    throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Installed/updated bundle version do not match what was installed/updated");
                 }
             }
         }
diff --git a/deploymentadmin/itest/pom.xml b/deploymentadmin/itest/pom.xml
new file mode 100644
index 0000000..8a08ee5
--- /dev/null
+++ b/deploymentadmin/itest/pom.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>felix-parent</artifactId>
+		<version>1.2.0</version>
+		<relativePath>../../pom/pom.xml</relativePath>
+	</parent>
+	<properties>
+		<osgi.version>4.2.0</osgi.version>
+		<pax.exam.version>2.4.0</pax.exam.version>
+		<pax.exam.plugin.version>1.2.4</pax.exam.plugin.version>
+		<pax.url.aether.version>1.4.0</pax.url.aether.version>
+		<pax.swissbox.version>1.3.1</pax.swissbox.version>
+		<pax.runner.version>1.7.6</pax.runner.version>
+	</properties>
+	<name>Apache Felix DeploymentAdmin Integration Tests</name>
+	<version>0.1.1-SNAPSHOT</version>
+	<artifactId>org.apache.felix.deploymentadmin.itest</artifactId>
+	<packaging>jar</packaging>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<version>${osgi.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>${osgi.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.deploymentadmin</artifactId>
+			<version>0.9.1-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.metatype</artifactId>
+			<version>1.0.5-SNAPSHOT</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.configadmin</artifactId>
+			<version>1.2.8</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.eventadmin</artifactId>
+			<version>1.2.14</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.10</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-junit4</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-container-forked</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.runner</groupId>
+			<artifactId>pax-runner-no-jcl</artifactId>
+			<version>${pax.runner.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-link-assembly</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.exam</groupId>
+			<artifactId>pax-exam-link-mvn</artifactId>
+			<version>${pax.exam.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.url</groupId>
+			<artifactId>pax-url-aether</artifactId>
+			<version>${pax.url.aether.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.ops4j.pax.url</groupId>
+			<artifactId>pax-url-wrap</artifactId>
+			<version>${pax.url.aether.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.inject</groupId>
+			<artifactId>javax.inject</artifactId>
+			<version>1</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.framework</artifactId>
+			<version>4.0.2</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+			<version>1.6.0</version>
+			<scope>compile</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<version>${osgi.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>${osgi.version}</version>
+		</dependency>
+
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<target>1.6</target>
+					<source>1.6</source>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
new file mode 100644
index 0000000..b44e357
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/BaseIntegrationTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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.itest;
+
+import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_TEST_BUNDLE;
+import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage;
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.felix;
+import static org.ops4j.pax.exam.CoreOptions.frameworkStartLevel;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.url;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Inject;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.junit.Before;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.Version;
+import org.osgi.service.deploymentadmin.DeploymentAdmin;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Provides a common base class for all deployment admin related integration tests.
+ */
+public abstract class BaseIntegrationTest extends TestCase {
+
+    protected static final int DEFAULT_TIMEOUT = 10000;
+
+    protected static final String TEST_SERVICE_NAME = "org.apache.felix.deploymentadmin.test.bundle1.TestService";
+    protected static final String TEST_FAILING_BUNDLE_RP1 = "org.apache.felix.deploymentadmin.test.rp1";
+
+    @Inject
+    protected volatile BundleContext m_context;
+    @Inject
+    protected volatile DeploymentAdmin m_deploymentAdmin;
+    @Inject
+    protected volatile PackageAdmin m_packageAdmin;
+
+    protected volatile AtomicInteger m_gate = new AtomicInteger(0);
+    protected volatile String m_testBundleBasePath;
+    protected volatile Map<String, List<Version>> m_initialBundles;
+    
+    private int cnt = 0;        
+    
+    @Configuration
+    public Option[] config() {
+        return options(
+            bootDelegationPackage("sun.*"),
+            cleanCaches(),
+            CoreOptions.systemProperty("logback.configurationFile").value("file:src/test/resources/logback.xml"),
+//            CoreOptions.vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787"),
+
+            mavenBundle("org.slf4j", "slf4j-api").version("1.6.5").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("ch.qos.logback", "logback-core").version("1.0.6").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("ch.qos.logback", "logback-classic").version("1.0.6").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+            url("link:classpath:META-INF/links/org.ops4j.pax.exam.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.exam.inject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.extender.service.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.base.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.core.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.extender.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.lifecycle.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.framework.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            url("link:classpath:META-INF/links/org.apache.geronimo.specs.atinject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+            mavenBundle("org.osgi", "org.osgi.core").version("4.2.0").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.osgi", "org.osgi.compendium").version("4.2.0").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.apache.felix", "org.apache.felix.dependencymanager").version("3.0.0").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.apache.felix", "org.apache.felix.deploymentadmin").version("0.9.1-SNAPSHOT").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.apache.felix", "org.apache.felix.eventadmin").version("1.2.14").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.apache.felix", "org.apache.felix.configadmin").version("1.2.8").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.apache.felix", "org.apache.felix.log").version("1.0.1").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+//            mavenBundle("org.apache.felix", "org.apache.felix.shell").version("1.4.3").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+//            mavenBundle("org.apache.felix", "org.apache.felix.shell.tui").version("1.4.1").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            
+            junitBundles(),
+            frameworkStartLevel(START_LEVEL_TEST_BUNDLE),
+            felix());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull("No bundle context?!", m_context);
+
+        File f = new File("../testbundles").getAbsoluteFile();
+        assertTrue("Failed to find test bundles directory?!", f.exists() && f.isDirectory());
+
+        m_testBundleBasePath = f.getAbsolutePath();
+
+        m_context.addFrameworkListener(new FrameworkListener() {
+            public void frameworkEvent(FrameworkEvent event) {
+                if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
+                    m_gate.getAndIncrement();
+                }
+            }
+        });
+        
+        m_initialBundles = new HashMap<String, List<Version>>();
+        
+        for (Bundle bundle : m_context.getBundles()) {
+            List<Version> versions = m_initialBundles.get(bundle.getSymbolicName());
+            if (versions == null) {
+                versions = new ArrayList<Version>();
+                m_initialBundles.put(bundle.getSymbolicName(), versions);
+            }
+            versions.add(bundle.getVersion());
+        }
+    }
+
+    protected void assertBundleExists(String symbolicName, String version) {
+        boolean result = isBundleAdded(symbolicName, version);
+        if (!result) {
+            fail("Bundle " + symbolicName + ", v" + version + " does not exist?!\nCurrent additional bundles are: " + getCurrentBundles());
+        }
+    }
+
+    protected void assertBundleNotExists(String symbolicName, String version) {
+        boolean result = isBundleAdded(symbolicName, version);
+        if (result) {
+            fail("Bundle " + symbolicName + ", v" + version + " does (still) exist?!\nCurrent additional bundles are: " + getCurrentBundles());
+        }
+    }
+
+    protected void assertDeploymentException(int expectedCode, DeploymentException exception) {
+        assertEquals("Invalid exception code?!\nException = " + exception, expectedCode, exception.getCode());
+    }
+
+    protected void awaitRefreshPackagesEvent() throws Exception {
+        long start = System.currentTimeMillis();
+        while ((m_gate.get() == 0) && ((System.currentTimeMillis() - start) < DEFAULT_TIMEOUT)) {
+            TimeUnit.MILLISECONDS.sleep(100);
+        }
+        assertTrue("Failed to obtain refresh packages event?! " + m_gate.get(), m_gate.get() > 0);
+        m_gate.set(0);
+    }
+
+    protected <T> T awaitService(String serviceName) throws Exception {
+        ServiceTracker tracker = new ServiceTracker(m_context, serviceName, null);
+        tracker.open();
+        T result;
+        try {
+            result = (T) tracker.waitForService(DEFAULT_TIMEOUT);
+        }
+        finally {
+            tracker.close();
+        }
+        return result;
+    }
+
+    protected DeploymentPackageBuilder createNewDeploymentPackageBuilder(String version) {
+        return createDeploymentPackageBuilder(String.format("itest%d", ++cnt), version);
+    }
+    
+    protected DeploymentPackageBuilder createDeploymentPackageBuilder(String symName, String version) {
+        return DeploymentPackageBuilder.create(symName, version);
+    }
+    
+    protected Map<String, List<Version>> getCurrentBundles() {
+        Map<String, List<Version>> bundles = new HashMap<String, List<Version>>();
+        for (Bundle bundle : m_context.getBundles()) {
+            String symbolicName = bundle.getSymbolicName();
+            Version version = bundle.getVersion();
+            
+            // Is is not part of any of the initially provisioned bundles?
+            List<Version> versions = m_initialBundles.get(symbolicName);
+            if ((versions == null) || !versions.contains(version)) {
+                List<Version> versions2 = bundles.get(symbolicName);
+                if (versions2 == null) {
+                    versions2 = new ArrayList<Version>();
+                    bundles.put(symbolicName, versions2);
+                }
+                versions2.add(version);
+            }
+        }
+        return bundles;
+    }
+    
+    protected String getSymbolicName(String baseName) {
+        return "testbundles.".concat(baseName);
+    }
+
+    protected URL getTestResource(String resourceName) {
+        if (!resourceName.startsWith("/")) {
+            resourceName = "/".concat(resourceName);
+        }
+        URL resource = getClass().getResource(resourceName);
+        assertNotNull("No such resource: " + resourceName, resource);
+        return resource;
+    }
+
+    /**
+     * @param baseName
+     * @return
+     * @throws MalformedURLException
+     */
+    protected URL getTestBundle(String baseName) throws MalformedURLException {
+        File f = new File(m_testBundleBasePath, String.format("%1$s/target/org.apache.felix.deploymentadmin.test.%1$s-1.0.0.jar", baseName));
+        assertTrue("No such bundle: " + f, f.exists() && f.isFile());
+        return f.toURI().toURL();
+    }
+    
+    protected boolean isBundleActive(Bundle bundle) {
+        return isBundleInState(bundle, Bundle.ACTIVE);
+    }
+
+    protected boolean isBundleAdded(String symbolicName, String version) {
+        return isBundleAdded(symbolicName, new Version(version));
+    }
+
+    protected boolean isBundleAdded(String symbolicName, Version version) {
+        Map<String, List<Version>> bundles = getCurrentBundles();
+
+        List<Version> availableVersions = bundles.get(symbolicName);
+        return (availableVersions != null) && availableVersions.contains(version);
+    }
+
+    protected boolean isBundleInstalled(Bundle bundle) {
+        return isBundleInState(bundle, Bundle.INSTALLED);
+    }
+
+    protected boolean isBundleInState(Bundle bundle, int state) {
+        return ((bundle.getState() & state) != 0);
+    }
+
+    protected boolean isBundleRemoved(String symbolicName, String version) {
+        return isBundleRemoved(symbolicName, new Version(version));
+    }
+    
+    protected boolean isBundleRemoved(String symbolicName, Version version) {
+        Map<String, List<Version>> bundles = getCurrentBundles();
+
+        List<Version> availableVersions = bundles.get(symbolicName);
+        return (availableVersions == null) || !availableVersions.contains(version);
+    }
+
+    protected boolean isBundleResolved(Bundle bundle) {
+        return isBundleInState(bundle, Bundle.RESOLVED);
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/CustomizerTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/CustomizerTest.java
new file mode 100644
index 0000000..35cdff9
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/CustomizerTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.itest;
+
+import static org.osgi.service.deploymentadmin.DeploymentException.*;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
+/**
+ * Provides test cases on the use of customizers in Deployment Admin. 
+ */
+@RunWith(JUnit4TestRunner.class)
+public class CustomizerTest extends BaseIntegrationTest {
+
+    /**
+     * Tests that if an exception is thrown during the commit-phase, the installation proceeds and succeeds.
+     */
+    @Test
+    public void testInstallBundleWithExceptionThrowingInCommitCauseNoRollbackOk() throws Exception {
+        System.setProperty("rp1", "commit");
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        // Though the commit failed; the package should be installed...
+        assertBundleExists(getSymbolicName("rp1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the prepare-phase, the installation is cancelled and rolled back.
+     */
+    @Test
+    public void testInstallBundleWithExceptionThrowingInPrepareCausesRollbackOk() throws Exception {
+        System.setProperty("rp1", "prepare");
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a failing deployment package?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(DeploymentException.CODE_COMMIT_ERROR, exception);
+        }
+
+        assertTrue("No bundles should be started!", getCurrentBundles().isEmpty());
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the processing of a resource, the installation is cancelled and rolled back.
+     */
+    @Test
+    public void testInstallResourceWithExceptionThrowingInProcessCausesRollbackOk() throws Exception {
+        System.setProperty("rp1", "process");
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a failing deployment package?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(DeploymentException.CODE_RESOURCE_SHARING_VIOLATION, exception);
+        }
+
+        assertTrue("No bundles should be started!", getCurrentBundles().isEmpty());
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the dropping of a resource, the installation is continued and finishes normally.
+     */
+    @Test
+    public void testDropResourceWithExceptionThrowingInDroppedCausesRollbackOk() throws Exception {
+        System.setProperty("rp1", "dropped");
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertTrue("One bundle should be started!", getCurrentBundles().size() == 1);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the commit-phase, the installation proceeds and succeeds.
+     */
+    @Test
+    public void testInstallResourceProcessorWithExceptionThrowingInStartCausesRollbackOk() throws Exception {
+        System.setProperty("rp1", "start");
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a failing RP?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_OTHER_ERROR, exception);
+        }
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+        assertTrue("Expected no artifacts to be installed?!", getCurrentBundles().isEmpty());
+    }
+
+    /**
+     * Tests that if a resource is installed which mentions a RP that does not belong to the same package, a rollback takes place.
+     */
+    @Test
+    public void testInstallResourceWithForeignCustomizerFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")));
+
+        m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+
+        awaitRefreshPackagesEvent();
+        
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+        assertBundleExists(getSymbolicName("rp1"), "1.0.0");
+
+        dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .disableVerification()
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a resource with an non-existing RP?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_FOREIGN_CUSTOMIZER, exception);
+        }
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+        assertTrue("Expected no additional artifacts to be installed?!", getCurrentBundles().size() == 1);
+    }
+
+    /**
+     * Tests that if a resource is installed which mentions a RP that does not exist a rollback takes place.
+     */
+    @Test
+    public void testInstallResourceWithNonAvailableCustomizerFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .disableVerification()
+            .add(dpBuilder.createResource().setResourceProcessorPID("my.unknown.rp").setUrl(getTestResource("test-config1.xml")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a resource with an non-existing RP?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_PROCESSOR_NOT_FOUND, exception);
+        }
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+        assertTrue("Expected no artifacts to be installed?!", getCurrentBundles().isEmpty());
+    }
+
+}
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
new file mode 100644
index 0000000..64c5689
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentAdminTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.itest;
+
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_BUNDLE_NAME_ERROR;
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_OTHER_ERROR;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder.JarManifestManipulatingFilter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.deploymentadmin.DeploymentAdmin;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Generic tests for {@link DeploymentAdmin}.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class DeploymentAdminTest extends BaseIntegrationTest {
+
+    @Test
+    public void testBundleSymbolicNameMustMatchManifestEntry() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle2"))
+                .setFilter(new JarManifestManipulatingFilter("Bundle-SymbolicName", "foo"))
+            );
+        
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a bundle with a fake symbolic name?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_BUNDLE_NAME_ERROR, exception);
+        }
+    }
+
+    @Test
+    public void testBundleVersionMustMatchManifestEntry() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle2"))
+                .setFilter(new JarManifestManipulatingFilter("Bundle-Version", "1.1.0"))
+            );
+        
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a bundle with a fake version?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_OTHER_ERROR, exception);
+        }
+    }
+
+    @Test
+    public void testManifestEntryMustMatchBundleSymbolicName() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setSymbolicName("foo")
+                .setUrl(getTestBundle("bundle2"))
+            );
+        
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a bundle with a fake symbolic name?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_BUNDLE_NAME_ERROR, exception);
+        }
+    }
+
+    @Test
+    public void testManifestEntryMustMatchBundleVersion() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setVersion("1.1.0")
+                .setUrl(getTestBundle("bundle2"))
+            );
+        
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a bundle with a fake version?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertDeploymentException(CODE_OTHER_ERROR, exception);
+        }
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentPackageBuilderTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentPackageBuilderTest.java
new file mode 100644
index 0000000..e38d538
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/DeploymentPackageBuilderTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.itest;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder.JarManifestManipulatingFilter;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for {@link DeploymentPackageBuilder}.
+ */
+public class DeploymentPackageBuilderTest extends TestCase {
+
+    private String m_testBundleBasePath;
+
+    @Before
+    public void setUp() throws Exception {
+        File f = new File("../testbundles").getAbsoluteFile();
+        assertTrue("Failed to find test bundles directory?!", f.exists() && f.isDirectory());
+
+        m_testBundleBasePath = f.getAbsolutePath();
+    }
+
+    /**
+     * Tests that we can build a deployment package with a bundle resource.
+     */
+    @Test
+    public void testCreateMissingBundleResourceOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = DeploymentPackageBuilder.create("dp-test", "1.0.0");
+        dpBuilder
+            .setFixPackage()
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1")).setMissing()
+            );
+
+        JarInputStream jis = new JarInputStream(dpBuilder.generate());
+        assertNotNull(jis);
+
+        Manifest manifest = jis.getManifest();
+        assertManifestHeader(manifest, "DeploymentPackage-SymbolicName", "dp-test");
+        assertManifestHeader(manifest, "DeploymentPackage-Version", "1.0.0");
+
+        String filename = getBundleName("bundle1");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.bundle1");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.0.0");
+        assertManifestEntry(manifest, filename, "DeploymentPackage-Missing", "true");
+
+        int count = countJarEntries(jis);
+
+        assertEquals("Expected two entries in the JAR!", 0, count);
+    }
+
+    /**
+     * Tests that we can build a deployment package with a bundle resource.
+     */
+    @Test
+    public void testCreateMinimalSingleBundleResourceOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = DeploymentPackageBuilder.create("dp-test", "1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            );
+
+        JarInputStream jis = new JarInputStream(dpBuilder.generate());
+        assertNotNull(jis);
+
+        Manifest manifest = jis.getManifest();
+        assertManifestHeader(manifest, "DeploymentPackage-SymbolicName", "dp-test");
+        assertManifestHeader(manifest, "DeploymentPackage-Version", "1.0.0");
+
+        String filename = getBundleName("bundle1");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.bundle1");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.0.0");
+
+        int count = countJarEntries(jis);
+
+        assertEquals("Expected two entries in the JAR!", 1, count);
+    }
+
+    /**
+     * Tests that we can filter a resource.
+     */
+    @Test
+    public void testResourceFilterOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = DeploymentPackageBuilder.create("dp-test", "1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle2")))
+            .add(dpBuilder.createBundleResource()
+                .setVersion("1.1.0")
+                .setFilter(new JarManifestManipulatingFilter("Bundle-Version", "1.1.0", "Foo", "bar"))
+                .setUrl(getTestBundle("bundle1")));
+
+        JarInputStream jis = new JarInputStream(dpBuilder.generate());
+        assertNotNull(jis);
+
+        Manifest manifest = jis.getManifest();
+        assertManifestHeader(manifest, "DeploymentPackage-SymbolicName", "dp-test");
+        assertManifestHeader(manifest, "DeploymentPackage-Version", "1.0.0");
+
+        String filename = getBundleName("bundle1");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.bundle1");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.1.0");
+
+        filename = getBundleName("bundle2");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.bundle2");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.0.0");
+
+        try {
+            byte[] buf = new byte[32 * 1024];
+
+            JarEntry entry;
+            while ((entry = jis.getNextJarEntry()) != null) {
+                if (entry.getName().endsWith("valid-bundle1.jar")) {
+                    int read = jis.read(buf);
+
+                    JarInputStream jis2 = new JarInputStream(new ByteArrayInputStream(Arrays.copyOf(buf, read)));
+                    Manifest manifest2 = jis2.getManifest();
+
+                    Attributes mainAttributes = manifest2.getMainAttributes();
+                    assertEquals("1.1.0", mainAttributes.getValue("Bundle-Version"));
+                    assertEquals("bar", mainAttributes.getValue("Foo"));
+                    
+                    jis2.close();
+                }
+                jis.closeEntry();
+            }
+        }
+        finally {
+            jis.close();
+        }
+    }
+
+    /**
+     * Tests that we can build a deployment package with a "plain" resource and resource processor.
+     */
+    @Test
+    public void testCreateMinimalSingleResourceAndProcessorOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = DeploymentPackageBuilder.create("dp-test", "1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource()
+                .setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource()
+                .setResourceProcessorPID("org.apache.felix.deploymentadmin.test.rp1")
+                .setUrl(getTestResource("test-config1.xml"))
+            );
+
+        JarInputStream jis = new JarInputStream(dpBuilder.generate());
+        assertNotNull(jis);
+
+        Manifest manifest = jis.getManifest();
+        assertManifestHeader(manifest, "DeploymentPackage-SymbolicName", "dp-test");
+        assertManifestHeader(manifest, "DeploymentPackage-Version", "1.0.0");
+
+        String filename = getBundleName("rp1");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.rp1");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.0.0");
+
+        filename = "test-config1.xml";
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Resource-Processor", "org.apache.felix.deploymentadmin.test.rp1");
+
+        int count = countJarEntries(jis);
+
+        assertEquals("Expected two entries in the JAR!", 2, count);
+    }
+
+    /**
+     * Tests that we can build a deployment package with two bundle resources.
+     */
+    @Test
+    public void testCreateMinimalTwoBundleResourcesOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = DeploymentPackageBuilder.create("dp-test", "1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle2"))
+            );
+
+        JarInputStream jis = new JarInputStream(dpBuilder.generate());
+        assertNotNull(jis);
+
+        Manifest manifest = jis.getManifest();
+        assertManifestHeader(manifest, "DeploymentPackage-SymbolicName", "dp-test");
+        assertManifestHeader(manifest, "DeploymentPackage-Version", "1.0.0");
+
+        String filename = getBundleName("bundle1");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.bundle1");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.0.0");
+
+        filename = getBundleName("bundle2");
+
+        assertManifestEntry(manifest, filename, "Name", filename);
+        assertManifestEntry(manifest, filename, "Bundle-SymbolicName", "testbundles.bundle2");
+        assertManifestEntry(manifest, filename, "Bundle-Version", "1.0.0");
+
+        int count = countJarEntries(jis);
+
+        assertEquals("Expected two entries in the JAR!", 2, count);
+    }
+
+    private void assertAttributes(Attributes attributes, String headerName, String expectedValue)
+        throws RuntimeException {
+        assertNotNull("No attributes given!", attributes);
+        assertEquals(headerName, expectedValue, attributes.getValue(headerName));
+    }
+
+    private void assertManifestEntry(Manifest manifest, String key, String headerName, String expectedValue)
+        throws RuntimeException {
+        Attributes attributes = manifest.getEntries().get(key);
+        assertNotNull("No attributes found for: " + key, attributes);
+        assertAttributes(attributes, headerName, expectedValue);
+    }
+
+    private void assertManifestHeader(Manifest manifest, String headerName, String expectedValue)
+        throws RuntimeException {
+        assertAttributes(manifest.getMainAttributes(), headerName, expectedValue);
+    }
+
+    private int countJarEntries(JarInputStream jis) throws IOException {
+        int count = 0;
+        try {
+            while (jis.getNextJarEntry() != null) {
+                count++;
+                jis.closeEntry();
+            }
+        }
+        finally {
+            jis.close();
+        }
+        return count;
+    }
+
+    private String getBundleName(String baseName) {
+        return String.format("org.apache.felix.deploymentadmin.test.%1$s-1.0.0.jar", baseName);
+    }
+
+    private String getBundleFilename(String baseName) {
+        return String.format("%1$s/target/org.apache.felix.deploymentadmin.test.%1$s-1.0.0.jar", baseName);
+    }
+
+    private URL getTestBundle(String baseName) throws MalformedURLException {
+        File f = new File(m_testBundleBasePath, getBundleFilename(baseName));
+        assertTrue("No such bundle: " + f, f.exists() && f.isFile());
+        return f.toURI().toURL();
+    }
+
+    private URL getTestResource(String resourceName) {
+        if (!resourceName.startsWith("/")) {
+            resourceName = "/".concat(resourceName);
+        }
+        URL resource = getClass().getResource(resourceName);
+        assertNotNull("No such resource: " + resourceName, resource);
+        return resource;
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java
new file mode 100644
index 0000000..5cee51e
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallDeploymentPackageTest.java
@@ -0,0 +1,302 @@
+/*
+ * 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.itest;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
+/**
+ * Provides test cases regarding the use of "normal" deployment packages in DeploymentAdmin.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class InstallDeploymentPackageTest extends BaseIntegrationTest {
+
+    /**
+     * Tests that adding the dependency for a bundle in an update package causes the depending bundle to be resolved and started.
+     */
+    @Test
+    public void testInstallBundleWithDependencyInPackageUpdateOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        // missing bundle1 as dependency...
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp1.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertTrue(isBundleInstalled(dp1.getBundle(getSymbolicName("bundle2"))));
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertTrue(isBundleActive(dp2.getBundle(getSymbolicName("bundle1"))));
+        assertTrue(isBundleActive(dp2.getBundle(getSymbolicName("bundle2"))));
+    }
+
+    /**
+     * Tests that installing a bundle with a dependency installed by another deployment package is not started, but is resolved.
+     */
+    @Test
+    public void testInstallBundleWithDependencyInSeparatePackageOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+
+        // We shouldn't be able to resolve the deps for bundle2...
+        assertFalse(m_packageAdmin.resolveBundles(new Bundle[] { dp1.getBundle(getSymbolicName("bundle2")) }));
+
+        assertTrue(isBundleInstalled(dp1.getBundle(getSymbolicName("bundle2"))));
+
+        dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        // as missing bundle1...
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+
+        // Now we should be able to resolve the dependencies for bundle2...
+        assertTrue(m_packageAdmin.resolveBundles(new Bundle[] { dp1.getBundle(getSymbolicName("bundle2")) }));
+
+        assertTrue(isBundleActive(dp2.getBundle(getSymbolicName("bundle1"))));
+        assertTrue(isBundleResolved(dp1.getBundle(getSymbolicName("bundle2"))));
+    }
+
+    /**
+     * Tests that if an exception is thrown in the start method of a bundle, the installation is not rolled back.
+     */
+    @Test
+    public void testInstallBundleWithExceptionThrownInStartCausesNoRollbackOk() throws Exception {
+        System.setProperty("bundle3", "start");
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle1"))));
+        // the bundle threw an exception during start, so it is not active...
+        assertFalse(isBundleActive(dp.getBundle(getSymbolicName("bundle3"))));
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that installing a bundle whose dependencies cannot be met, is installed, but not started.
+     */
+    @Test
+    public void testInstallBundleWithMissingDependencyOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+
+        assertTrue(isBundleInstalled(dp.getBundle(getSymbolicName("bundle2"))));
+    }
+
+    /**
+     * Tests that installing a bundle along with other (non-bundle) artifacts succeeds.
+     */
+    @Test
+    public void testInstallBundleWithOtherArtifactsOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(
+                dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1)
+                    .setUrl(getTestResource("test-config1.xml")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        // Though the commit failed; the package should be installed...
+        assertBundleExists(getSymbolicName("rp1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that installing a new bundle works as expected.
+     */
+    @Test
+    public void testInstallSingleValidBundleOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertNotNull("Failed to obtain test service?!", awaitService(TEST_SERVICE_NAME));
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle1"))));
+    }
+
+    /**
+     * Tests that installing two bundles works as expected.
+     */
+    @Test
+    public void testInstallTwoValidBundlesOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertNotNull("Failed to obtain test service?!", awaitService(TEST_SERVICE_NAME));
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle1"))));
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle2"))));
+    }
+
+    /**
+     * Tests that if an exception is thrown during the stop of a bundle, the installation/update continues and succeeds.
+     */
+    @Test
+    public void testUpdateBundleWithExceptionThrownInStopCauseNoRollbackOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        System.setProperty("bundle3", "stop");
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle1"))));
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle2"))));
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle3"))));
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the uninstall of a bundle, the installation/update continues and succeeds.
+     */
+    @Test
+    public void testUninstallBundleWithExceptionThrownInStopCauseNoRollbackOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        System.setProperty("bundle3", "stop");
+
+        dpBuilder = dpBuilder.create("1.0.1");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+        assertBundleNotExists(getSymbolicName("bundle3"), "1.0.0");
+
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle1"))));
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle2"))));
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallFixPackageTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallFixPackageTest.java
new file mode 100644
index 0000000..7d94550
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/InstallFixPackageTest.java
@@ -0,0 +1,548 @@
+/*
+ * 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.itest;
+
+import static org.osgi.service.deploymentadmin.DeploymentException.*;
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_MISSING_BUNDLE;
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_MISSING_FIXPACK_TARGET;
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_MISSING_RESOURCE;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
+/**
+ * Provides test cases regarding the use of "fix-packages" in DeploymentAdmin.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class InstallFixPackageTest extends BaseIntegrationTest {
+
+    /**
+     * Tests that we can install a new bundle through a fix-package.
+     */
+    @Test
+    public void testInstallBundleWithDependencyInFixPackageUpdateOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp1.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertEquals(Bundle.INSTALLED, bundle.getState());
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        bundle = dp2.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from bundle context?!", bundle);
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+
+        assertTrue(isBundleActive(bundle));
+    }
+
+    /**
+     * Tests that it is not possible to install a fix package if it specifies a fix-version range that falls outside the installed target deployment package.
+     */
+    @Test
+    public void testInstallFixPackageOutsideLowerTargetRangeFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp1.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertEquals(Bundle.INSTALLED, bundle.getState());
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("(1.0,2.0)") // should not include version 1.0.0!
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing fix package for undefined target package?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(CODE_MISSING_FIXPACK_TARGET, exception);
+        }
+    }
+
+    /**
+     * Tests that it is not possible to install a fix package if it specifies a fix-version range that falls outside the installed target deployment package.
+     */
+    @Test
+    public void testInstallFixPackageOutsideUpperTargetRangeFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp1.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertEquals(Bundle.INSTALLED, bundle.getState());
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[0.9,1.0)") // should not include version 1.0.0!
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing fix package for undefined target package?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(CODE_MISSING_FIXPACK_TARGET, exception);
+        }
+    }
+
+    /**
+     * Tests that a fix package can only be installed after at least one version of the denoted target package is installed.
+     */
+    @Test
+    public void testInstallFixPackageWithoutTargetFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Should not be able to install fix package without target?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(CODE_MISSING_FIXPACK_TARGET, exception);
+        }
+    }
+
+    /**
+     * Tests that installing a fix-package causes the original target package to be replaced.
+     */
+    @Test
+    public void testInstallFixPackageReplacesOriginalTargetPackageOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        assertEquals("Expected only a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        awaitRefreshPackagesEvent();
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected only a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that installing a fix-package that mentions a bundle that is not in the target package fails.
+     */
+    @Test
+    public void testInstallFixPackageWithMissingTargetBundleFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        // missed valid-bundle1 as dependency...
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        assertEquals("Expected only a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        awaitRefreshPackagesEvent();
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a fix-package with a missing bundle on target?!");
+        }
+        catch (DeploymentException exception) {
+            assertDeploymentException(CODE_MISSING_BUNDLE, exception);
+        }
+    }
+
+    /**
+     * Tests that installing a fix-package that mentions a resource that is not in the target package fails.
+     */
+    @Test
+    public void testInstallFixPackageWithMissingTargetResourceFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        assertEquals("Expected only a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        awaitRefreshPackagesEvent();
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")).setMissing());
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a fix-package with a missing bundle on target?!");
+        }
+        catch (DeploymentException exception) {
+            assertDeploymentException(CODE_MISSING_RESOURCE, exception);
+        }
+    }
+
+    /**
+     * Tests that installing a fix-package that mentions a resource processor that is not in the target package fails.
+     */
+    @Test
+    public void testInstallFixPackageWithMissingTargetResourceProcessorFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        assertEquals("Expected only a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        awaitRefreshPackagesEvent();
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")).setMissing())
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a fix-package with a missing bundle on target?!");
+        }
+        catch (DeploymentException exception) {
+            assertDeploymentException(CODE_MISSING_BUNDLE, exception);
+        }
+    }
+
+    /**
+     * Tests that installing a fix-package that mentions a bundle that does exist (in another DP), but is not in the target package fails.
+     */
+    @Test
+    public void testInstallFixPackageWithMissingTargetBundleFromOtherPackageFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected only a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected only a single deployment package?!", 2, m_deploymentAdmin.listDeploymentPackages().length);
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")).setMissing())
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Succeeded into installing a fix-package with a missing bundle on target?!");
+        }
+        catch (DeploymentException exception) {
+            assertDeploymentException(CODE_BUNDLE_SHARING_VIOLATION, exception);
+        }
+    }
+
+    /**
+     * Tests that only in a fix-package bundle can be marked as missing.
+     */
+    @Test
+    public void testMissingBundlesOnlyInFixPackageFail() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp1.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertEquals(Bundle.INSTALLED, bundle.getState());
+
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .disableVerification()
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        try {
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Failed to install missing bundle?!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertEquals("Invalid exception code?!", CODE_BAD_HEADER, exception.getCode());
+        }
+    }
+
+    /**
+     * Tests the removal of a bundle through a fix package.
+     */
+    @Test
+    public void testRemoveBundleInFixPackageUpdateOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        Bundle bundle = dp1.getBundle(getSymbolicName("bundle2"));
+        assertNotNull("Failed to obtain bundle from deployment package?!", bundle);
+
+        assertEquals(Bundle.ACTIVE, bundle.getState());
+
+        // valid-bundle2 is to be removed by this fix package...
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")).setMissing());
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleNotExists(getSymbolicName("bundle2"), "1.0.0");
+    }
+
+    /**
+     * Tests that we can uninstall a fix-package.
+     */
+    @Test
+    public void testUninstallBundleAddedInFixPackageOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        // Add valid-bundle1 through fix-package...
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+
+        // Uninstall the deployment package; should yield the original situation again...
+        dp2.uninstall();
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+
+        // None of our installed bundles should remain...
+        assertBundleNotExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleNotExists(getSymbolicName("bundle2"), "1.0.0");
+    }
+
+    /**
+     * Tests that we can uninstall a fix-package.
+     */
+    @Test
+    public void testUninstallBundleRemovedInFixPackageOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        // remove valid-bundle1 through fix package...
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleNotExists(getSymbolicName("bundle2"), "1.0.0");
+
+        // Uninstall the deployment package; should yield the initial situation again...
+        dp2.uninstall();
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+
+        // None of our installed bundles should remain...
+        assertBundleNotExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleNotExists(getSymbolicName("bundle2"), "1.0.0");
+    }
+
+    /**
+     * Tests that we can uninstall a fix-package and that this will only uninstall the bundles installed by the fix-package.
+     */
+    @Test
+    public void testUninstallFixPackageOnlyRemovesOwnArtifactsOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")));
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")));
+
+        DeploymentPackage dp2 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp2);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected two deployment packages?!", 2, m_deploymentAdmin.listDeploymentPackages().length);
+
+        // add bundle2 through fix package...
+        dpBuilder = createDeploymentPackageBuilder(dpBuilder.getSymbolicName(), "1.0.1");
+        dpBuilder
+            .setFixPackage("[1.0,2.0)")
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle2")).setMissing());
+
+        DeploymentPackage dp3 = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp3);
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected two deployment packages?!", 2, m_deploymentAdmin.listDeploymentPackages().length);
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle2"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        // Uninstall the deployment package; should yield the initial situation again...
+        dp3.uninstall();
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected a single deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        // None of our installed bundles should remain...
+        assertBundleNotExists(getSymbolicName("bundle3"), "1.0.0");
+        assertBundleNotExists(getSymbolicName("bundle2"), "1.0.0");
+        // The bundle installed in another deployment package should still remain...
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/ResourceSharingTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/ResourceSharingTest.java
new file mode 100644
index 0000000..1ccac49
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/ResourceSharingTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.itest;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder.JarManifestManipulatingFilter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.service.deploymentadmin.DeploymentAdmin;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
+/**
+ * Generic tests for {@link DeploymentAdmin}.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class ResourceSharingTest extends BaseIntegrationTest {
+
+    @Test
+    public void testBundleCanBelongToOneDeploymentPackageOnly() throws Exception {
+        DeploymentPackageBuilder dpBuilder1 = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder1
+            .add(dpBuilder1.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder1.createBundleResource()
+                .setUrl(getTestBundle("bundle2"))
+            );
+
+        DeploymentPackageBuilder dpBuilder2 = createNewDeploymentPackageBuilder("0.8.0");
+        dpBuilder2
+            .add(dpBuilder2.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            );
+
+        DeploymentPackage dp1 = m_deploymentAdmin.installDeploymentPackage(dpBuilder1.generate());
+        assertNotNull("No deployment package returned?!", dp1);
+
+        awaitRefreshPackagesEvent();
+
+        try {
+            // should fail: valid-bundle1 belongs to another DP...
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder2.generate());
+            fail("Expected a DeploymentException with code " + DeploymentException.CODE_BUNDLE_SHARING_VIOLATION);
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertEquals(DeploymentException.CODE_BUNDLE_SHARING_VIOLATION, exception.getCode());
+        }
+    }
+
+    @Test
+    public void testBundleCannotBeSharedWithNonDeploymentPackagedBundle() throws Exception {
+        // Manually install a bundle...
+        Bundle result = m_context.installBundle(getTestBundle("bundle1").toExternalForm());
+        assertNotNull(result);
+        
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle1"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle2"))
+            );
+
+        try {
+            // should fail: valid-bundle1 is installed, but does not belong to any DP...
+            m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+            fail("Expected a DeploymentException with code " + DeploymentException.CODE_BUNDLE_SHARING_VIOLATION);
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected...
+            assertEquals(DeploymentException.CODE_BUNDLE_SHARING_VIOLATION, exception.getCode());
+        }
+    }
+
+    @Test
+    public void testForeignBundleCanCoexistWithPackagedBundleIfVersionsDiffer() throws Exception {
+        // Manually install a bundle...
+        Bundle result = m_context.installBundle(getTestBundle("bundle1").toExternalForm());
+        assertNotNull(result);
+
+        long bundleId = result.getBundleId();
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertTrue(isBundleInstalled(result));
+
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource()
+                .setVersion("1.1.0")
+                .setUrl(getTestBundle("bundle1"))
+                .setFilter(new JarManifestManipulatingFilter("Bundle-Version", "1.1.0"))
+            )
+            .add(dpBuilder.createBundleResource()
+                .setUrl(getTestBundle("bundle2"))
+            );
+        
+        // should succeed: valid-bundle1 is installed, but has a different version than the one in our DP...
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle1"), "1.0.0");
+        assertBundleExists(getSymbolicName("bundle1"), "1.1.0");
+        
+        // The manually installed bundle should still be in the installed or resolved state...
+        assertTrue(isBundleInstalled(m_context.getBundle(bundleId)) || isBundleResolved(m_context.getBundle(bundleId)));
+        assertTrue(isBundleActive(dp.getBundle(getSymbolicName("bundle1"))));
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/UninstallDeploymentPackageTest.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/UninstallDeploymentPackageTest.java
new file mode 100644
index 0000000..21f4266
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/UninstallDeploymentPackageTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.itest;
+
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_COMMIT_ERROR;
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_OTHER_ERROR;
+import static org.osgi.service.deploymentadmin.DeploymentException.CODE_PROCESSOR_NOT_FOUND;
+
+import org.apache.felix.deploymentadmin.itest.util.DeploymentPackageBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
+/**
+ * Provides test cases regarding the use of "normal" deployment packages in DeploymentAdmin.
+ */
+@RunWith(JUnit4TestRunner.class)
+public class UninstallDeploymentPackageTest extends BaseIntegrationTest {
+
+    /**
+     * Tests that if a resource processor is missing (uninstalled) during the forced uninstallation of a deployment package this will ignored and the uninstall completes.
+     */
+    @Test
+    public void testForcedUninstallDeploymentPackageWithMissingResourceProcessorSucceeds() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        Bundle rpBundle = dp.getBundle(getSymbolicName("rp1"));
+        rpBundle.uninstall();
+
+        assertTrue("One bundle should be started!", getCurrentBundles().size() == 1);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        assertTrue(dp.uninstallForced());
+        
+        assertTrue("No bundle should be started!", getCurrentBundles().isEmpty());
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the commit-phase, the installation is continued normally.
+     */
+    @Test
+    public void testUninstallDeploymentPackageWithExceptionThrowingInCommitCausesNoRollbackOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+        
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+        
+        System.setProperty("rp1", "commit");
+
+        dp.uninstall();
+
+        assertTrue("No bundles should be started! " + getCurrentBundles(), getCurrentBundles().isEmpty());
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the dropping of a resource, the installation is rolled back.
+     */
+    @Test
+    public void testUninstallDeploymentPackageWithExceptionThrowingInDropAllResourcesCausesRollbackOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+        
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+        
+        System.setProperty("rp1", "dropAllResources");
+
+        try {
+            dp.uninstall();
+            fail("Expected uninstall to fail and rollback!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(CODE_OTHER_ERROR, exception);
+        }
+        
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the prepare-phase, the installation is rolled back.
+     */
+    @Test
+    public void testUninstallDeploymentPackageWithExceptionThrowingInPrepareCausesRollbackOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+        
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+        
+        System.setProperty("rp1", "prepare");
+
+        try {
+            dp.uninstall();
+            fail("Expected uninstall to fail and rollback!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(CODE_COMMIT_ERROR, exception);
+        }
+        
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+
+    /**
+     * Tests that if an exception is thrown during the uninstall of a bundle, the installation/update continues and succeeds.
+     */
+    @Test
+    public void testUninstallDeploymentPackageWithExceptionThrownInStopCauseNoRollbackOk() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle3")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertBundleExists(getSymbolicName("bundle3"), "1.0.0");
+
+        System.setProperty("bundle3", "stop");
+        
+        dp.uninstall();
+
+        awaitRefreshPackagesEvent();
+
+        assertEquals("Expected no deployment package?!", 0, m_deploymentAdmin.listDeploymentPackages().length);
+        
+        assertTrue("Expected no bundles to remain?!", getCurrentBundles().isEmpty());
+    }
+
+    /**
+     * Tests that if a resource processor is missing (uninstalled) during the uninstallation of a deployment package, this is regarded an error and a rollback is performed.
+     */
+    @Test
+    public void testUninstallDeploymentPackageWithMissingResourceProcessorCausesRollback() throws Exception {
+        DeploymentPackageBuilder dpBuilder = createNewDeploymentPackageBuilder("1.0.0");
+        dpBuilder
+            .add(dpBuilder.createBundleResource().setUrl(getTestBundle("bundle1")))
+            .add(dpBuilder.createResourceProcessorResource().setUrl(getTestBundle("rp1")))
+            .add(dpBuilder.createResource().setResourceProcessorPID(TEST_FAILING_BUNDLE_RP1).setUrl(getTestResource("test-config1.xml")));
+
+        DeploymentPackage dp = m_deploymentAdmin.installDeploymentPackage(dpBuilder.generate());
+        assertNotNull("No deployment package returned?!", dp);
+
+        awaitRefreshPackagesEvent();
+
+        assertTrue("Two bundles should be started!", getCurrentBundles().size() == 2);
+
+        Bundle rpBundle = dp.getBundle(getSymbolicName("rp1"));
+        rpBundle.uninstall();
+
+        assertTrue("One bundle should be started!", getCurrentBundles().size() == 1);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+
+        try {
+            dp.uninstall();
+            fail("Expected uninstall to fail and rollback!");
+        }
+        catch (DeploymentException exception) {
+            // Ok; expected
+            assertDeploymentException(CODE_PROCESSOR_NOT_FOUND, exception);
+        }
+        
+        assertTrue("One bundle should be started!", getCurrentBundles().size() == 1);
+
+        assertEquals("Expected no deployment package?!", 1, m_deploymentAdmin.listDeploymentPackages().length);
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java
new file mode 100644
index 0000000..518bfca
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactData.java
@@ -0,0 +1,98 @@
+/*
+ * 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.itest.util;
+
+import java.net.URL;
+
+public class ArtifactData {
+
+    private final URL m_url;
+    private boolean m_isBundle;
+    private String m_filename;
+    private String m_bundleSymbolicName;
+    private String m_bundleVersion;
+    private boolean m_isCustomizer;
+    private String m_processorPID;
+    private boolean m_missing;
+    private ResourceFilter m_filter;
+
+    ArtifactData(URL url, String filename) {
+        m_url = url;
+        m_filename = filename;
+    }
+
+    public String getFilename() {
+        return m_filename;
+    }
+
+    public ResourceFilter getFilter() {
+        return m_filter;
+    }
+
+    public String getProcessorPid() {
+        return m_processorPID;
+    }
+
+    public String getSymbolicName() {
+        return m_bundleSymbolicName;
+    }
+
+    public URL getURL() {
+        return m_url;
+    }
+
+    public String getVersion() {
+        return m_bundleVersion;
+    }
+
+    public boolean isBundle() {
+        return m_isBundle;
+    }
+
+    public boolean isCustomizer() {
+        return m_isCustomizer;
+    }
+
+    public boolean isMissing() {
+        return m_missing;
+    }
+
+    void setArtifactResourceProcessor(String processorPID) {
+        m_processorPID = processorPID;
+    }
+
+    void setBundleMetadata(String bundleSymbolicName, String bundleVersion) {
+        m_isBundle = true;
+        m_bundleSymbolicName = bundleSymbolicName;
+        m_bundleVersion = bundleVersion;
+    }
+
+    void setFilter(ResourceFilter filter) {
+        m_filter = filter;
+    }
+
+    void setMissing(boolean missing) {
+        m_missing = missing;
+    }
+
+    void setResourceProcessor(String processorPID) {
+        m_isCustomizer = true;
+        m_processorPID = processorPID;
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactDataBuilder.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactDataBuilder.java
new file mode 100644
index 0000000..e175444
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ArtifactDataBuilder.java
@@ -0,0 +1,71 @@
+/**
+ * 
+ */
+package org.apache.felix.deploymentadmin.itest.util;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Provides a builder for creating {@link ArtifactData} instances.
+ */
+public abstract class ArtifactDataBuilder<TYPE extends ArtifactDataBuilder<?>> {
+    
+    protected URL m_url;
+    protected String m_filename;
+    protected ResourceFilter m_filter;
+    protected boolean m_missing;
+    
+    ArtifactDataBuilder() {
+        m_filter = null;
+        m_missing = false;
+    }
+    
+    public TYPE setFilename(String filename) {
+        m_filename = filename;
+        return getThis();
+    }
+    
+    public TYPE setFilter(ResourceFilter filter) {
+        m_filter = filter;
+        return getThis();
+    }
+    
+    public TYPE setMissing() {
+        return setMissing(true);
+    }
+    
+    public TYPE setMissing(boolean missing) {
+        m_missing = missing;
+        return getThis();
+    }
+
+    public TYPE setUrl(String url) throws MalformedURLException {
+        return setUrl(new URL(url));
+    }
+ 
+    public TYPE setUrl(URL url) {
+        m_url = url;
+        return getThis();
+    }
+    
+    ArtifactData build() {
+        validate();
+
+        ArtifactData result = new ArtifactData(m_url, m_filename);
+        result.setFilter(m_filter);
+        result.setMissing(m_missing);
+        return result;
+    }
+    
+    abstract TYPE getThis(); 
+    
+    void validate() throws RuntimeException {
+        if (m_url == null) {
+            throw new RuntimeException("URL is missing!");
+        }
+        if (m_filename == null || "".equals(m_filename.trim())) {
+            throw new RuntimeException("Filename is missing!");
+        }
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/BundleDataBuilder.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/BundleDataBuilder.java
new file mode 100644
index 0000000..ea8ca2d
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/BundleDataBuilder.java
@@ -0,0 +1,105 @@
+/**
+ * 
+ */
+package org.apache.felix.deploymentadmin.itest.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+/**
+ * Provides a bundle data builder.
+ */
+public class BundleDataBuilder<TYPE extends ArtifactDataBuilder<?>> extends ArtifactDataBuilder<TYPE> {
+
+    private String m_symbolicName;
+    private String m_version;
+
+    public BundleDataBuilder() {
+        super();
+    }
+
+    public TYPE setSymbolicName(String symbolicName) {
+        m_symbolicName = symbolicName;
+        return getThis();
+    }
+
+    public TYPE setVersion(String version) {
+        m_version = version;
+        return getThis();
+    }
+
+    @Override
+    ArtifactData build() {
+        ArtifactData result = super.build();
+        result.setBundleMetadata(m_symbolicName, m_version);
+        return result;
+    }
+    
+    String getRequiredHeader(Attributes mainAttributes, String headerName) throws RuntimeException {
+        String value = mainAttributes.getValue(headerName);
+        if (value == null || value.equals("")) {
+            throw new RuntimeException("Missing or invalid " + headerName + " header.");
+        }
+        return value;
+    }
+
+    @Override
+    TYPE getThis() {
+        return (TYPE) this;
+    }
+
+    void retrieveAndSetBundleInformation() throws RuntimeException {
+        JarInputStream jis = null;
+        try {
+            jis = new JarInputStream(m_url.openStream());
+
+            Manifest bundleManifest = jis.getManifest();
+            if (bundleManifest == null) {
+                throw new RuntimeException("Not a valid manifest in: " + m_url);
+            }
+
+            Attributes attributes = bundleManifest.getMainAttributes();
+
+            if (m_symbolicName == null || "".equals(m_symbolicName.trim())) {
+                setSymbolicName(getRequiredHeader(attributes, "Bundle-SymbolicName"));
+            }
+
+            if (m_version == null || "".equals(m_version.trim())) {
+                setVersion(getRequiredHeader(attributes, "Bundle-Version"));
+            }
+            
+            if (m_filename == null || "".equals(m_filename.trim())) {
+                setFilename(new File(m_url.getFile()).getName());
+            }
+            
+            setAdditionalBundleInformation(bundleManifest);
+        }
+        catch (IOException exception) {
+            throw new RuntimeException("Failed to retrieve bundle information; set symbolic name and version!", exception);
+        }
+        finally {
+            if (jis != null) {
+                try {
+                    jis.close();
+                }
+                catch (IOException exception) {
+                    // Ignore...
+                }
+            }
+        }
+    }
+
+    void setAdditionalBundleInformation(Manifest bundleManifest) {
+        // Nop
+    }
+
+    @Override
+    void validate() throws RuntimeException {
+        retrieveAndSetBundleInformation();
+        
+        super.validate();
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java
new file mode 100644
index 0000000..49e9330
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/DeploymentPackageBuilder.java
@@ -0,0 +1,389 @@
+/*
+ * 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.itest.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.osgi.framework.Version;
+
+/**
+ * Builder for deployment packages. Can handle bundles, resource processors and artifacts.
+ */
+public class DeploymentPackageBuilder {
+
+    /**
+     * Convenience resource filter for manipulating JAR manifests.
+     */
+    public abstract static class JarManifestFilter implements ResourceFilter {
+
+        public final InputStream createInputStream(URL url) throws IOException {
+            byte[] buffer = new byte[BUFFER_SIZE];
+
+            JarInputStream jis = new JarInputStream(url.openStream());
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            JarOutputStream jos = new JarOutputStream(baos, filterManifest(jis.getManifest()));
+
+            JarEntry input;
+            while ((input = jis.getNextJarEntry()) != null) {
+                jos.putNextEntry(input);
+                int read;
+                while ((read = jis.read(buffer)) > 0) {
+                    jos.write(buffer, 0, read);
+                }
+                jos.closeEntry();
+            }
+            jos.close();
+
+            return new ByteArrayInputStream(baos.toByteArray());
+        }
+        
+        protected abstract Manifest filterManifest(Manifest manifest);
+    }
+    
+    /**
+     * Simple manifest JAR manipulator implementation.
+     */
+    public static class JarManifestManipulatingFilter extends JarManifestFilter {
+        private final String[] m_replacementEntries;
+        
+        public JarManifestManipulatingFilter(String... replacementEntries) {
+            if (replacementEntries == null || ((replacementEntries.length) % 2 != 0)) {
+                throw new IllegalArgumentException("Entries must be a multiple of two!");
+            }
+            m_replacementEntries = Arrays.copyOf(replacementEntries, replacementEntries.length);
+        }
+
+        @Override
+        protected Manifest filterManifest(Manifest manifest) {
+            for (int i = 0; i < m_replacementEntries.length; i += 2) {
+                String key = m_replacementEntries[i];
+                String value = m_replacementEntries[i+1];
+                manifest.getMainAttributes().putValue(key, value);
+            }
+            return manifest;
+        }
+    }
+    
+    private static final int BUFFER_SIZE = 32 * 1024;
+
+    private final String m_symbolicName;
+    private final String m_version;
+    private final List<ArtifactData> m_bundles = new ArrayList<ArtifactData>();
+    private final List<ArtifactData> m_processors = new ArrayList<ArtifactData>();
+
+    private final List<ArtifactData> m_artifacts = new ArrayList<ArtifactData>();
+    private String m_fixPackageVersion;
+
+    private boolean m_verification;
+
+    private DeploymentPackageBuilder(String symbolicName, String version) {
+        m_symbolicName = symbolicName;
+        m_version = version;
+        
+        m_verification = true;
+    }
+
+    /**
+     * Creates a new deployment package builder.
+     * 
+     * @param name the name of the deployment package
+     * @param version the version of the deployment package
+     * @return a builder to further add data to the deployment package
+     */
+    public static DeploymentPackageBuilder create(String name, String version) {
+        return new DeploymentPackageBuilder(name, version);
+    }
+
+    /**
+     * Adds an artifact to the deployment package.
+     * 
+     * @param builder the artifact data builder to use.
+     * @return this builder.
+     * @throws Exception if something goes wrong while building the artifact.
+     */
+    public DeploymentPackageBuilder add(ArtifactDataBuilder builder) throws Exception {
+        ArtifactData artifactData = builder.build();
+        if (artifactData.isCustomizer()) {
+            m_processors.add(artifactData);
+        }
+        else if (artifactData.isBundle()) {
+            m_bundles.add(artifactData);
+        }
+        else {
+            m_artifacts.add(artifactData);
+        }
+        return this;
+    }
+
+    /**
+     * Creates a new deployment package builder with the same symbolic name as this builder.
+     * 
+     * @param name the name of the deployment package
+     * @param version the version of the deployment package
+     * @return a builder to further add data to the deployment package
+     */
+    public DeploymentPackageBuilder create(String version) {
+        return new DeploymentPackageBuilder(getSymbolicName(), version);
+    }
+
+    public BundleDataBuilder createBundleResource() {
+        return new BundleDataBuilder();
+    }
+
+    public ResourceDataBuilder createResource() {
+        return new ResourceDataBuilder();
+    }
+
+    public ResourceProcessorDataBuilder createResourceProcessorResource() {
+        return new ResourceProcessorDataBuilder();
+    }
+
+    /**
+     * Disables the verification of the generated deployment package, potentially causing an erroneous result to be generated.
+     *  
+     * @return this builder.
+     */
+    public DeploymentPackageBuilder disableVerification() {
+        m_verification = false;
+        return this;
+    }
+
+    /**
+     * Generates a deployment package and streams it to the output stream you provide. Before
+     * it starts generating, it will first validate that you have actually specified a
+     * resource processor for each type of artifact you provided.
+     * 
+     * @return the input stream containing the deployment package.
+     * @throws Exception if something goes wrong while validating or generating
+     */
+    public InputStream generate() throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        generate(baos);
+
+        return new ByteArrayInputStream(baos.toByteArray());
+    }
+
+    /**
+     * Generates a deployment package and streams it to the output stream you provide. Before
+     * it starts generating, it will first validate that you have actually specified a
+     * resource processor for each type of artifact you provided.
+     * 
+     * @param output the output stream to write to
+     * @throws Exception if something goes wrong while validating or generating
+     */
+    public void generate(OutputStream output) throws Exception {
+        List<ArtifactData> artifacts = new ArrayList<ArtifactData>();
+        artifacts.addAll(m_bundles);
+        artifacts.addAll(m_processors);
+        artifacts.addAll(m_artifacts);
+        
+        if (m_verification) {
+            validateProcessedArtifacts();
+            validateMissingArtifacts(artifacts);
+        }
+        
+        Manifest m = createManifest(artifacts);
+        writeStream(artifacts, m, output);
+    }
+
+    /**
+     * @return the symbolic name of the deployment package.
+     */
+    public String getSymbolicName() {
+        return m_symbolicName;
+    }
+
+    /**
+     * @return the version of the deployment package.
+     */
+    public String getVersion() {
+        return m_version;
+    }
+
+    /**
+     * Marks this deployment package as a 'fix' package.
+     * 
+     * @return this builder.
+     */
+    public DeploymentPackageBuilder setFixPackage() {
+        Version v1 = new Version(m_version);
+        Version v2 = new Version(v1.getMajor() + 1, 0, 0);
+        String version = String.format("[%d.%d, %d.%d)", v1.getMajor(), v1.getMinor(), v2.getMajor(), v2.getMinor());
+        return setFixPackage(version);
+    }
+
+    /**
+     * Marks this deployment package as a 'fix' package.
+     * 
+     * @param versionRange the version range in which this fix-package should be applied.
+     * @return this builder.
+     */
+    public DeploymentPackageBuilder setFixPackage(String versionRange) {
+        m_fixPackageVersion = versionRange;
+        return this;
+    }
+
+    private Manifest createManifest(List<ArtifactData> files) throws Exception {
+        Manifest manifest = new Manifest();
+        Attributes main = manifest.getMainAttributes();
+        main.putValue("Manifest-Version", "1.0");
+        main.putValue("DeploymentPackage-SymbolicName", m_symbolicName);
+        main.putValue("DeploymentPackage-Version", m_version);
+
+        if ((m_fixPackageVersion != null) && !"".equals(m_fixPackageVersion)) {
+            main.putValue("DeploymentPackage-FixPack", m_fixPackageVersion);
+        }
+
+        Map<String, Attributes> entries = manifest.getEntries();
+
+        Iterator<ArtifactData> filesIter = files.iterator();
+        while (filesIter.hasNext()) {
+            ArtifactData file = filesIter.next();
+
+            Attributes a = new Attributes();
+            a.putValue("Name", file.getFilename());
+
+            if (file.isBundle()) {
+                a.putValue("Bundle-SymbolicName", file.getSymbolicName());
+                a.putValue("Bundle-Version", file.getVersion());
+                if (file.isCustomizer()) {
+                    a.putValue("DeploymentPackage-Customizer", "true");
+                    a.putValue("Deployment-ProvidesResourceProcessor", file.getProcessorPid());
+                }
+            }
+            else {
+                a.putValue("Resource-Processor", file.getProcessorPid());
+            }
+
+            if (file.isMissing()) {
+                a.putValue("DeploymentPackage-Missing", "true");
+            }
+
+            entries.put(file.getFilename(), a);
+        }
+
+        return manifest;
+    }
+    
+    private void validateMissingArtifacts(List<ArtifactData> files) throws Exception {
+        boolean missing = false;
+        
+        Iterator<ArtifactData> artifactIter = files.iterator();
+        while (artifactIter.hasNext() && !missing) {
+            ArtifactData data = artifactIter.next();
+            
+            if (data.isMissing()) {
+                missing = true;
+            }
+        }
+        
+        if (missing && (m_fixPackageVersion == null || "".equals(m_fixPackageVersion))) {
+            throw new Exception("Artifact cannot be missing without a fix package version!");
+        }
+    }
+
+    private void validateProcessedArtifacts() throws Exception {
+        Iterator<ArtifactData> artifactIter = m_artifacts.iterator();
+        while (artifactIter.hasNext()) {
+            ArtifactData data = artifactIter.next();
+            String pid = data.getProcessorPid();
+            boolean found = false;
+
+            Iterator<ArtifactData> processorIter = m_processors.iterator();
+            while (processorIter.hasNext()) {
+                ArtifactData processor = processorIter.next();
+                if (pid.equals(processor.getProcessorPid())) {
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                throw new Exception("No resource processor found for artifact " + data.getURL()
+                    + " with processor PID " + pid);
+            }
+        }
+    }
+
+    private void writeStream(List<ArtifactData> files, Manifest manifest, OutputStream outputStream) throws Exception {
+        JarOutputStream output = null;
+        InputStream fis = null;
+        try {
+            output = new JarOutputStream(outputStream, manifest);
+            byte[] buffer = new byte[BUFFER_SIZE];
+
+            Iterator<ArtifactData> filesIter = files.iterator();
+            while (filesIter.hasNext()) {
+                ArtifactData file = filesIter.next();
+                if (file.isMissing()) {
+                    // No need to write the 'missing' files...
+                    continue;
+                }
+
+                output.putNextEntry(new JarEntry(file.getFilename()));
+
+                ResourceFilter filter = file.getFilter();
+                if (filter != null) {
+                    fis = filter.createInputStream(file.getURL());
+                }
+                else {
+                    fis = file.getURL().openStream();
+                }
+
+                try {
+                    int bytes = fis.read(buffer);
+                    while (bytes != -1) {
+                        output.write(buffer, 0, bytes);
+                        bytes = fis.read(buffer);
+                    }
+                }
+                finally {
+                    fis.close();
+                    fis = null;
+
+                    output.closeEntry();
+                }
+            }
+        }
+        finally {
+            if (fis != null) {
+                fis.close();
+            }
+            if (output != null) {
+                output.close();
+            }
+        }
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceDataBuilder.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceDataBuilder.java
new file mode 100644
index 0000000..c6cae3b
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceDataBuilder.java
@@ -0,0 +1,47 @@
+/**
+ * 
+ */
+package org.apache.felix.deploymentadmin.itest.util;
+
+import java.io.File;
+
+/**
+ * Provides a resource data builder.
+ */
+public class ResourceDataBuilder extends ArtifactDataBuilder<ResourceDataBuilder> {
+    
+    private String m_resourceProcessorPID;
+
+    public ResourceDataBuilder() {
+        super();
+    }
+    
+    public ResourceDataBuilder setResourceProcessorPID(String resourceProcessorPID) {
+        m_resourceProcessorPID = resourceProcessorPID;
+        return this;
+    }
+    
+    @Override ArtifactData build() {
+        ArtifactData result = super.build();
+        result.setArtifactResourceProcessor(m_resourceProcessorPID);
+        return result;
+    }
+
+    @Override
+    ResourceDataBuilder getThis() {
+        return this;
+    }
+
+    @Override
+    void validate() throws RuntimeException {
+        if (m_resourceProcessorPID == null || "".equals(m_resourceProcessorPID.trim())) {
+            throw new RuntimeException("Artifact resource processor PID is missing!");
+        }
+
+        if (m_filename == null || "".equals(m_filename.trim())) {
+            setFilename(new File(m_url.getFile()).getName());
+        }
+
+        super.validate();
+    }
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceFilter.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceFilter.java
new file mode 100644
index 0000000..5ce03dd
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceFilter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.itest.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+public interface ResourceFilter {
+    
+    InputStream createInputStream(URL url) throws IOException;
+    
+}
diff --git a/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceProcessorDataBuilder.java b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceProcessorDataBuilder.java
new file mode 100644
index 0000000..66b561f
--- /dev/null
+++ b/deploymentadmin/itest/src/test/java/org/apache/felix/deploymentadmin/itest/util/ResourceProcessorDataBuilder.java
@@ -0,0 +1,51 @@
+/**
+ * 
+ */
+package org.apache.felix.deploymentadmin.itest.util;
+
+import java.util.jar.Manifest;
+
+/**
+ * Provides a resource processor data builder.
+ */
+public class ResourceProcessorDataBuilder extends BundleDataBuilder<ResourceProcessorDataBuilder> {
+    
+    private String m_resourceProcessorPID;
+
+    public ResourceProcessorDataBuilder() {
+        super();
+    }
+    
+    public ResourceProcessorDataBuilder setResourceProcessorPID(String resourceProcessorPID) {
+        m_resourceProcessorPID = resourceProcessorPID;
+        return this;
+    }
+    
+    @Override ArtifactData build() {
+        ArtifactData result = super.build();
+        result.setResourceProcessor(m_resourceProcessorPID);
+        return result;
+    }
+
+    @Override
+    ResourceProcessorDataBuilder getThis() {
+        return this;
+    }
+
+    @Override
+    void setAdditionalBundleInformation(Manifest bundleManifest) {
+        String processorPID = getRequiredHeader(bundleManifest.getMainAttributes(), "Deployment-ProvidesResourceProcessor");
+        if (m_resourceProcessorPID == null || "".equals(m_resourceProcessorPID.trim())) {
+            setResourceProcessorPID(processorPID);
+        }        
+    }
+    
+    @Override
+    void validate() throws RuntimeException {
+        super.validate();
+        
+        if (m_resourceProcessorPID == null || "".equals(m_resourceProcessorPID.trim())) {
+            throw new RuntimeException("Resource processor PID is missing!");
+        }
+    }
+}
diff --git a/deploymentadmin/itest/src/test/resources/logback.xml b/deploymentadmin/itest/src/test/resources/logback.xml
new file mode 100644
index 0000000..fa644da
--- /dev/null
+++ b/deploymentadmin/itest/src/test/resources/logback.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<root level="WARN">
+		<appender-ref ref="STDOUT" />
+	</root>
+
+	<logger name="org.ops4j" level="WARN" />
+</configuration>
diff --git a/deploymentadmin/itest/src/test/resources/test-config1.xml b/deploymentadmin/itest/src/test/resources/test-config1.xml
new file mode 100644
index 0000000..458bcae
--- /dev/null
+++ b/deploymentadmin/itest/src/test/resources/test-config1.xml
@@ -0,0 +1,12 @@
+<MetaData xmlns='http://www.osgi.org/xmlns/metatype/v1.0.0'>
+	<OCD name='ocd' id='ocd'>
+		<AD id='default_group' type='STRING' cardinality='0' />
+	</OCD>
+	<Designate pid='test.service.pid' bundle="osgi-dp:adele.openid.manager">
+		<Object ocdref='ocd'>
+			<Attribute adref='default_group'>
+				<Value><![CDATA[openidusers]]></Value>
+			</Attribute>
+		</Object>
+	</Designate>
+</MetaData>
\ No newline at end of file
diff --git a/deploymentadmin/pom.xml b/deploymentadmin/pom.xml
index f23335f..1aab8fe 100644
--- a/deploymentadmin/pom.xml
+++ b/deploymentadmin/pom.xml
@@ -32,5 +32,7 @@
     <modules>
         <module>deploymentadmin</module>
         <module>autoconf</module>
+        <module>testbundles</module>
+        <module>itest</module>
     </modules>
 </project>
diff --git a/deploymentadmin/testbundles/bundle1/bnd.bnd b/deploymentadmin/testbundles/bundle1/bnd.bnd
new file mode 100644
index 0000000..05e274f
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle1/bnd.bnd
@@ -0,0 +1,5 @@
+Bundle-Version: 1.0.0
+Bundle-SymbolicName: testbundles.bundle1
+Bundle-Activator: org.apache.felix.deploymentadmin.test.bundle1.impl.Activator
+Private-Package: org.apache.felix.deploymentadmin.test.bundle1.impl
+Export-Package: org.apache.felix.deploymentadmin.test.bundle1
diff --git a/deploymentadmin/testbundles/bundle1/pom.xml b/deploymentadmin/testbundles/bundle1/pom.xml
new file mode 100644
index 0000000..9e4d6e3
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle1/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>org.apache.felix.deploymentadmin.testbundles</artifactId>
+		<version>1</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<name>Apache Felix DeploymentAdmin Test Bundle 1</name>
+	<version>1.0.0</version>
+	<artifactId>org.apache.felix.deploymentadmin.test.bundle1</artifactId>
+	<packaging>bundle</packaging>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<version>2.3.4</version>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<_include>bnd.bnd</_include>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/TestService.java b/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/TestService.java
new file mode 100644
index 0000000..2effe5a
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/TestService.java
@@ -0,0 +1,31 @@
+/*
+ * 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.test.bundle1;
+
+/**
+ * Provides a test service.
+ */
+public interface TestService {
+    /**
+     * Does something.
+     * 
+     * @return a string result, never <code>null</code>.
+     */
+    String doIt();
+}
diff --git a/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/impl/Activator.java b/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/impl/Activator.java
new file mode 100644
index 0000000..a175823
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/impl/Activator.java
@@ -0,0 +1,34 @@
+/*
+ * 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.test.bundle1.impl;
+
+import org.apache.felix.deploymentadmin.test.bundle1.TestService;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+    public void start(BundleContext context) throws Exception {
+        context.registerService(TestService.class.getName(), new TestServiceImpl(), null);
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        // No-op
+    }
+}
diff --git a/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/impl/TestServiceImpl.java b/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/impl/TestServiceImpl.java
new file mode 100644
index 0000000..581d7dc
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle1/src/main/java/org/apache/felix/deploymentadmin/test/bundle1/impl/TestServiceImpl.java
@@ -0,0 +1,31 @@
+/*
+ * 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.test.bundle1.impl;
+
+import org.apache.felix.deploymentadmin.test.bundle1.TestService;
+
+/**
+ * Provides a default implementation for {@link TestService}.
+ */
+public class TestServiceImpl implements TestService {
+
+    public String doIt() {
+        return "Hello World!";
+    }
+}
diff --git a/deploymentadmin/testbundles/bundle2/bnd.bnd b/deploymentadmin/testbundles/bundle2/bnd.bnd
new file mode 100644
index 0000000..2c82fd2
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle2/bnd.bnd
@@ -0,0 +1,6 @@
+Bundle-Version: 1.0.0
+Bundle-SymbolicName: testbundles.bundle2
+Bundle-Activator: org.apache.felix.deploymentadmin.test.bundle2.impl.Activator
+Private-Package: org.apache.felix.deploymentadmin.test.bundle2.impl
+Import-Package: org.apache.felix.deploymentadmin.test.bundle1, org.osgi.framework, org.osgi.util.tracker, *
+
diff --git a/deploymentadmin/testbundles/bundle2/pom.xml b/deploymentadmin/testbundles/bundle2/pom.xml
new file mode 100644
index 0000000..2ef211b
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle2/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>org.apache.felix.deploymentadmin.testbundles</artifactId>
+		<version>1</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<name>Apache Felix DeploymentAdmin Test Bundle 2</name>
+	<version>1.0.0</version>
+	<artifactId>org.apache.felix.deploymentadmin.test.bundle2</artifactId>
+	<packaging>bundle</packaging>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>${pom.groupId}</groupId>
+			<artifactId>org.apache.felix.deploymentadmin.test.bundle1</artifactId>
+			<version>1.0.0</version>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<version>2.3.4</version>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<_include>bnd.bnd</_include>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/deploymentadmin/testbundles/bundle2/src/main/java/org/apache/felix/deploymentadmin/test/bundle2/impl/Activator.java b/deploymentadmin/testbundles/bundle2/src/main/java/org/apache/felix/deploymentadmin/test/bundle2/impl/Activator.java
new file mode 100644
index 0000000..cc17983
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle2/src/main/java/org/apache/felix/deploymentadmin/test/bundle2/impl/Activator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.test.bundle2.impl;
+
+import org.apache.felix.deploymentadmin.test.bundle1.TestService;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public class Activator implements BundleActivator, ServiceTrackerCustomizer {
+
+    ServiceTracker m_tracker;
+    BundleContext m_context;
+
+    public void start(BundleContext context) throws Exception {
+        m_context = context;
+
+        m_tracker = new ServiceTracker(context, TestService.class.getName(), this);
+        m_tracker.open();
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        // Nop
+        m_tracker.close();
+    }
+
+    public Object addingService(ServiceReference reference) {
+        Object service = m_context.getService(reference);
+        System.out.println("Service added: " + service);
+        return service;
+    }
+
+    public void modifiedService(ServiceReference reference, Object service) {
+        System.out.println("Service modified: " + service);
+    }
+
+    public void removedService(ServiceReference reference, Object service) {
+        System.out.println("Service removed: " + service);
+    }
+}
diff --git a/deploymentadmin/testbundles/bundle3/bnd.bnd b/deploymentadmin/testbundles/bundle3/bnd.bnd
new file mode 100644
index 0000000..55d93bc
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle3/bnd.bnd
@@ -0,0 +1,5 @@
+Bundle-Version: 1.0.0
+Bundle-SymbolicName: testbundles.bundle3
+Bundle-Activator: org.apache.felix.deploymentadmin.test.bundle3.impl.Activator
+Private-Package: org.apache.felix.deploymentadmin.test.bundle3.impl
+
diff --git a/deploymentadmin/testbundles/bundle3/pom.xml b/deploymentadmin/testbundles/bundle3/pom.xml
new file mode 100644
index 0000000..6e0e675
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle3/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>org.apache.felix.deploymentadmin.testbundles</artifactId>
+		<version>1</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<name>Apache Felix DeploymentAdmin Test Bundle 3</name>
+	<version>1.0.0</version>
+	<artifactId>org.apache.felix.deploymentadmin.test.bundle3</artifactId>
+	<packaging>bundle</packaging>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<version>2.3.4</version>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<_include>bnd.bnd</_include>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/deploymentadmin/testbundles/bundle3/src/main/java/org/apache/felix/deploymentadmin/test/bundle3/impl/Activator.java b/deploymentadmin/testbundles/bundle3/src/main/java/org/apache/felix/deploymentadmin/test/bundle3/impl/Activator.java
new file mode 100644
index 0000000..cbc3e80
--- /dev/null
+++ b/deploymentadmin/testbundles/bundle3/src/main/java/org/apache/felix/deploymentadmin/test/bundle3/impl/Activator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.test.bundle3.impl;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Throws an exception upon start or stop, depending on a system property.
+ */
+public class Activator implements BundleActivator {
+
+    public void start(BundleContext context) throws Exception {
+        checkMethodRuntimeFailure("start");
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        checkMethodRuntimeFailure("stop");
+    }
+    
+    private void checkMethodRuntimeFailure(String methodName) {
+        if (shouldFail(methodName)) {
+            throw new RuntimeException(methodName + " fails forcedly!");
+        }
+    }
+    
+    private boolean shouldFail(String methodName) {
+        String value = System.getProperty("bundle3", "");
+        return methodName.equals(value);
+    }
+
+}
diff --git a/deploymentadmin/testbundles/pom.xml b/deploymentadmin/testbundles/pom.xml
new file mode 100644
index 0000000..5b0c91e
--- /dev/null
+++ b/deploymentadmin/testbundles/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>felix-parent</artifactId>
+		<version>1.2.0</version>
+		<relativePath>../../pom/pom.xml</relativePath>
+	</parent>
+	<properties>
+		<osgi.version>4.2.0</osgi.version>
+	</properties>
+	<name>Apache Felix DeploymentAdmin Test Bundles</name>
+	<artifactId>org.apache.felix.deploymentadmin.testbundles</artifactId>
+	<version>1</version>
+	<packaging>pom</packaging>
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>org.osgi</groupId>
+				<artifactId>org.osgi.core</artifactId>
+				<version>${osgi.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.osgi</groupId>
+				<artifactId>org.osgi.compendium</artifactId>
+				<version>${osgi.version}</version>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+	<modules>
+		<module>bundle1</module>
+		<module>bundle2</module>
+		<module>bundle3</module>
+		<module>rp1</module>
+		<module>rp2</module>
+	</modules>
+</project>
diff --git a/deploymentadmin/testbundles/rp1/bnd.bnd b/deploymentadmin/testbundles/rp1/bnd.bnd
new file mode 100644
index 0000000..ab9642b
--- /dev/null
+++ b/deploymentadmin/testbundles/rp1/bnd.bnd
@@ -0,0 +1,6 @@
+Bundle-Version: 1.0.0
+Bundle-SymbolicName: testbundles.rp1
+Bundle-Activator: org.apache.felix.deploymentadmin.test.rp1.impl.Activator
+Private-Package: org.apache.felix.deploymentadmin.test.rp1.impl
+DeploymentPackage-Customizer: true
+Deployment-ProvidesResourceProcessor: org.apache.felix.deploymentadmin.test.rp1
diff --git a/deploymentadmin/testbundles/rp1/pom.xml b/deploymentadmin/testbundles/rp1/pom.xml
new file mode 100644
index 0000000..6517dcc
--- /dev/null
+++ b/deploymentadmin/testbundles/rp1/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>org.apache.felix.deploymentadmin.testbundles</artifactId>
+		<version>1</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<name>Apache Felix DeploymentAdmin Test RP 1</name>
+	<version>1.0.0</version>
+	<artifactId>org.apache.felix.deploymentadmin.test.rp1</artifactId>
+	<packaging>bundle</packaging>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<version>2.3.4</version>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<_include>bnd.bnd</_include>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/deploymentadmin/testbundles/rp1/src/main/java/org/apache/felix/deploymentadmin/test/rp1/impl/Activator.java b/deploymentadmin/testbundles/rp1/src/main/java/org/apache/felix/deploymentadmin/test/rp1/impl/Activator.java
new file mode 100644
index 0000000..009b709
--- /dev/null
+++ b/deploymentadmin/testbundles/rp1/src/main/java/org/apache/felix/deploymentadmin/test/rp1/impl/Activator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.test.rp1.impl;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.deploymentadmin.spi.DeploymentSession;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+
+/**
+ * Provides a bundle activator for registering the resource processor.
+ */
+public class Activator implements BundleActivator, ResourceProcessor {
+
+    public void start(BundleContext context) throws Exception {
+        checkMethodRuntimeFailure("start");
+
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "org.apache.felix.deploymentadmin.test.rp1");
+        
+        context.registerService(ResourceProcessor.class.getName(), this, props);
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        checkMethodRuntimeFailure("stop");
+    }
+
+    public void begin(DeploymentSession session) {
+        checkMethodRuntimeFailure("begin");
+    }
+
+    public void process(String name, InputStream stream) throws ResourceProcessorException {
+        checkMethodFailure(ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION, "process");
+    }
+
+    public void dropped(String resource) throws ResourceProcessorException {
+        checkMethodFailure(ResourceProcessorException.CODE_OTHER_ERROR, "dropped");
+    }
+
+    public void dropAllResources() throws ResourceProcessorException {
+        checkMethodFailure(ResourceProcessorException.CODE_OTHER_ERROR, "dropAllResources");
+    }
+
+    public void prepare() throws ResourceProcessorException {
+        checkMethodFailure(ResourceProcessorException.CODE_PREPARE, "prepare");
+    }
+
+    public void commit() {
+        checkMethodRuntimeFailure("commit");
+    }
+
+    public void rollback() {
+        checkMethodRuntimeFailure("rollback");
+    }
+
+    public void cancel() {
+        checkMethodRuntimeFailure("cancel");
+    }
+    
+    private void checkMethodFailure(int code, String methodName) throws ResourceProcessorException {
+        if (shouldFail(methodName)) {
+            throw new ResourceProcessorException(code, methodName + " fails forcedly!");
+        }
+    }
+    
+    private void checkMethodRuntimeFailure(String methodName) {
+        if (shouldFail(methodName)) {
+            throw new RuntimeException(methodName + " fails forcedly!");
+        }
+    }
+    
+    private boolean shouldFail(String methodName) {
+        String value = System.getProperty("rp1", "");
+        return methodName.equals(value);
+    }
+}
diff --git a/deploymentadmin/testbundles/rp2/bnd.bnd b/deploymentadmin/testbundles/rp2/bnd.bnd
new file mode 100644
index 0000000..a9d03cb
--- /dev/null
+++ b/deploymentadmin/testbundles/rp2/bnd.bnd
@@ -0,0 +1,6 @@
+Bundle-Version: 1.0.0
+Bundle-SymbolicName: testbundles.rp2
+Bundle-Activator: org.apache.felix.deploymentadmin.test.rp2.impl.Activator
+Private-Package: org.apache.felix.deploymentadmin.test.rp2.impl
+DeploymentPackage-Customizer: true
+Deployment-ProvidesResourceProcessor: org.apache.felix.deploymentadmin.test.rp2
diff --git a/deploymentadmin/testbundles/rp2/pom.xml b/deploymentadmin/testbundles/rp2/pom.xml
new file mode 100644
index 0000000..3f07541
--- /dev/null
+++ b/deploymentadmin/testbundles/rp2/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>org.apache.felix.deploymentadmin.testbundles</artifactId>
+		<version>1</version>
+		<relativePath>../pom.xml</relativePath>
+	</parent>
+	<properties>
+		<osgi.version>4.2.0</osgi.version>
+	</properties>
+	<name>Apache Felix DeploymentAdmin Test RP 2</name>
+	<version>1.0.0</version>
+	<artifactId>org.apache.felix.deploymentadmin.test.rp2</artifactId>
+	<packaging>bundle</packaging>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<version>2.3.4</version>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<_include>bnd.bnd</_include>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/deploymentadmin/testbundles/rp2/src/main/java/org/apache/felix/deploymentadmin/test/rp2/impl/Activator.java b/deploymentadmin/testbundles/rp2/src/main/java/org/apache/felix/deploymentadmin/test/rp2/impl/Activator.java
new file mode 100644
index 0000000..4ee24ba
--- /dev/null
+++ b/deploymentadmin/testbundles/rp2/src/main/java/org/apache/felix/deploymentadmin/test/rp2/impl/Activator.java
@@ -0,0 +1,100 @@
+/*
+ * 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.test.rp2.impl;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.deploymentadmin.spi.DeploymentSession;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+
+/**
+ * Provides a bundle activator and resource processor implementation that can delay methods for a 
+ * certain amount of time (defined by system property "rp2.delay", defaults to 2000 milliseconds).
+ */
+public class Activator implements BundleActivator, ResourceProcessor {
+
+    public void start(BundleContext context) throws Exception {
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, "org.apache.felix.deploymentadmin.bundle.rp2");
+
+        context.registerService(ResourceProcessor.class.getName(), this, props);
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        delayMethod("stop");
+    }
+
+    public void begin(DeploymentSession session) {
+        delayMethod("begin");
+    }
+
+    public void process(String name, InputStream stream) throws ResourceProcessorException {
+        delayMethod("process");
+    }
+
+    public void dropped(String resource) throws ResourceProcessorException {
+        delayMethod("dropped");
+    }
+
+    public void dropAllResources() throws ResourceProcessorException {
+        delayMethod("dropAllResources");
+    }
+
+    public void prepare() throws ResourceProcessorException {
+        delayMethod("prepare");
+    }
+
+    public void commit() {
+        delayMethod("commit");
+    }
+
+    public void rollback() {
+        delayMethod("rollback");
+    }
+
+    public void cancel() {
+        delayMethod("cancel");
+    }
+
+    private void delayMethod(String methodName) {
+        try {
+            if (shouldDelayMethod(methodName)) {
+                String value = System.getProperty("rp2.delay", "2000");
+
+                Thread.sleep(Long.parseLong(value));
+            }
+        }
+        catch (NumberFormatException exception) {
+            exception.printStackTrace();
+        }
+        catch (InterruptedException exception) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    private boolean shouldDelayMethod(String methodName) {
+        String value = System.getProperty("rp2", "");
+        return methodName.equals(value);
+    }
+}