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