FELIX-3139 Implemented first version of uninstall. Does not follow the spec yet to the letter, but functionally does the right thing.
FELIX-1829 Fixed the NPE.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1177325 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
index f0288c3..4a9cd33 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
@@ -41,7 +41,11 @@
  * deployment package data is obtained, this should be handled by extending classes.
  */
 public abstract class AbstractDeploymentPackage implements DeploymentPackage {
+    private static final String[] STRINGS = new String[] {};
+    private static final ResourceInfoImpl[] RESOURCE_INFO_IMPLS = new ResourceInfoImpl[] {};
+    private static final BundleInfoImpl[] BUNDLE_INFO_IMPLS = new BundleInfoImpl[] {};
     private final BundleContext m_bundleContext;
+    private final DeploymentAdminImpl m_deploymentAdmin;
     private final DeploymentPackageManifest m_manifest;
     private final Map m_nameToBundleInfo = new HashMap();
     private final Map m_pathToEntry = new HashMap();
@@ -49,26 +53,28 @@
     private final ResourceInfoImpl[] m_resourceInfos;
     private final String[] m_resourcePaths;
     private final boolean m_isFixPackage;
-    protected static final AbstractDeploymentPackage emptyPackage = new AbstractDeploymentPackage() {
+    private boolean m_isStale;
+    protected static final AbstractDeploymentPackage EMPTY_PACKAGE = new AbstractDeploymentPackage() {
         public String getHeader(String header) {
             if (Constants.DEPLOYMENTPACKAGE_SYMBOLICMAME.equals(header)) { return ""; }
             else if (Constants.DEPLOYMENTPACKAGE_VERSION.equals(header)) { return Version.emptyVersion.toString(); }
             else { return null; }
         }
         public Bundle getBundle(String symbolicName) { return null; }
-        public BundleInfo[] getBundleInfos() { return new BundleInfoImpl[] {}; }
-        public BundleInfoImpl[] getBundleInfoImpls() { return new BundleInfoImpl[] {}; }
+        public BundleInfo[] getBundleInfos() { return BUNDLE_INFO_IMPLS; }
+        public BundleInfoImpl[] getBundleInfoImpls() { return BUNDLE_INFO_IMPLS; }
+        public ResourceInfoImpl[] getResourceInfos() { return RESOURCE_INFO_IMPLS; }
         public String getName() { return ""; }
         public String getResourceHeader(String resource, String header) { return null; }
         public ServiceReference getResourceProcessor(String resource) { return null; }
-        public String[] getResources() { return new String[] {}; }
+        public String[] getResources() { return STRINGS; }
         public Version getVersion() { return Version.emptyVersion; }
         public boolean isStale() { return true; }
         public void uninstall() throws DeploymentException { throw new IllegalStateException("Can not uninstall stale DeploymentPackage"); }
         public boolean uninstallForced() throws DeploymentException { throw new IllegalStateException("Can not uninstall stale DeploymentPackage"); }
         public InputStream getBundleStream(String symbolicName) throws IOException { return null; }
-        public BundleInfoImpl[] getOrderedBundleInfos() { return new BundleInfoImpl[] {}; }
-        public ResourceInfoImpl[] getOrderedResourceInfos() { return new ResourceInfoImpl[] {}; }
+        public BundleInfoImpl[] getOrderedBundleInfos() { return BUNDLE_INFO_IMPLS; }
+        public ResourceInfoImpl[] getOrderedResourceInfos() { return RESOURCE_INFO_IMPLS; }
         public InputStream getCurrentEntryStream() { throw new UnsupportedOperationException(); }
         public AbstractInfo getNextEntry() throws IOException { throw new UnsupportedOperationException(); }
         public String getDisplayName() { return ""; }
@@ -83,6 +89,7 @@
         m_resourceInfos = null;
         m_resourcePaths = null;
         m_isFixPackage = false;
+        m_deploymentAdmin = null;
     }
 
     /**
@@ -92,10 +99,11 @@
      * @param bundleContext The bundle context.
      * @throws DeploymentException Thrown if the specified manifest does not describe a valid deployment package.
      */
-    public AbstractDeploymentPackage(Manifest manifest, BundleContext bundleContext) throws DeploymentException {
+    public AbstractDeploymentPackage(Manifest manifest, BundleContext bundleContext, DeploymentAdminImpl deploymentAdmin) throws DeploymentException {
         m_manifest = new DeploymentPackageManifest(manifest);
         m_isFixPackage = m_manifest.getFixPackage() != null;
         m_bundleContext = bundleContext;
+        m_deploymentAdmin = deploymentAdmin;
         m_bundleInfos = (BundleInfoImpl[]) m_manifest.getBundleInfos().toArray(new BundleInfoImpl[0]);
         for(int i = 0; i < m_bundleInfos.length; i++) {
             m_nameToBundleInfo.put(m_bundleInfos[i].getSymbolicName(), m_bundleInfos[i]);
@@ -115,7 +123,7 @@
         if (m_nameToBundleInfo.containsKey(symbolicName)) {
             Bundle[] bundles = m_bundleContext.getBundles();
             for (int i = 0; i < bundles.length; i++) {
-                if (bundles[i].getSymbolicName().equals(symbolicName)) {
+                if (symbolicName.equals(bundles[i].getSymbolicName())) {
                     return bundles[i];
                 }
             }
@@ -236,15 +244,23 @@
     }
 
     public boolean isStale() {
-        return false;
+        return m_isStale;
+    }
+    
+    public void setStale(boolean isStale) {
+        m_isStale = isStale;
     }
 
     public void uninstall() throws DeploymentException {
-        throw new IllegalStateException("Not implemented");
+        if (isStale()) {
+            throw new IllegalStateException("Deployment package is stale, cannot uninstall.");
+        }
+        m_deploymentAdmin.uninstallDeploymentPackage(this);
+        setStale(true);
     }
 
     public boolean uninstallForced() throws DeploymentException {
-        throw new IllegalStateException("Not implemented");
+        throw new IllegalStateException("Not implemented, use uninstall() for now.");
     }
 
     /**
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
index 2c30654..b099016 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
@@ -104,7 +104,7 @@
                     try {
                         File index = new File(packages[i], PACKAGEINDEX_FILE);
                         File contents = new File(packages[i], PACKAGECONTENTS_DIR);
-                        FileDeploymentPackage dp = new FileDeploymentPackage(index, contents, m_context);
+                        FileDeploymentPackage dp = new FileDeploymentPackage(index, contents, m_context, this);
                         m_packages.put(dp.getName(), dp);
                     }
                     catch (IOException e) {
@@ -155,7 +155,7 @@
         }
         try {
             if (!m_semaphore.tryAcquire(TIMEOUT)) {
-                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to install deployment package (" + TIMEOUT + "msec)");
+                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to install deployment package (" + TIMEOUT + " ms)");
             }
         }
         catch (InterruptedException ie) {
@@ -191,13 +191,13 @@
                 m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid Jar", e);
                 throw new DeploymentException(DeploymentException.CODE_NOT_A_JAR, "Stream does not contain a valid Jar", e);
             }
-            source = new StreamDeploymentPackage(jarInput, m_context);
+            source = new StreamDeploymentPackage(jarInput, m_context, this);
             sendStartedEvent(source.getName());
             
             AbstractDeploymentPackage target = (AbstractDeploymentPackage) getDeploymentPackage(source.getName());
             boolean newPackage = (target == null);
             if (newPackage) {
-                target = AbstractDeploymentPackage.emptyPackage;
+                target = AbstractDeploymentPackage.EMPTY_PACKAGE;
             }
             if (source.isFixPackage() && ((newPackage) || (!source.getVersionRange().isInRange(target.getVersion())))) {
                 succeeded = false;
@@ -238,7 +238,7 @@
             }
             FileDeploymentPackage fileDeploymentPackage = null;
             try {
-                fileDeploymentPackage = new FileDeploymentPackage(targetIndex, targetContents, m_context);
+                fileDeploymentPackage = new FileDeploymentPackage(targetIndex, targetContents, m_context, this);
                 m_packages.put(source.getName(), fileDeploymentPackage);
             }
             catch (IOException e) {
@@ -259,6 +259,41 @@
             m_semaphore.release();
         }
     }
+    
+    public void uninstallDeploymentPackage(DeploymentPackage dp) throws DeploymentException {
+        try {
+            if (!m_semaphore.tryAcquire(TIMEOUT)) {
+                throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to uninstall deployment package (" + TIMEOUT + " ms)");
+            }
+        }
+        catch (InterruptedException ie) {
+            throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Thread interrupted");
+        }
+        boolean succeeded = false;
+        AbstractDeploymentPackage source = AbstractDeploymentPackage.EMPTY_PACKAGE;
+        AbstractDeploymentPackage target = (AbstractDeploymentPackage) dp;
+        try {
+            try {
+                m_session = new DeploymentSessionImpl(source, target, m_commandChain, this);
+                m_session.call();
+            }
+            catch (DeploymentException de) {
+                succeeded = false;
+                throw de;
+            }
+
+            File targetPackage = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName());
+            delete(targetPackage);
+            m_packages.remove(dp.getName());
+            succeeded = true;
+        }
+        finally {
+            if (source != null) {
+                sendCompleteEvent(source.getName(), succeeded);
+            }
+            m_semaphore.release();
+        }
+    }
 
     private void delete(File target) {
     	if (target.isDirectory()) {
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java
index 8e37b54..a369d04 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java
@@ -34,8 +34,7 @@
 /**
  * Implementation of a <code>DeploymentPackage</code> that is persisted on disk.
  */
-class FileDeploymentPackage extends AbstractDeploymentPackage {
-
+public class FileDeploymentPackage extends AbstractDeploymentPackage {
     private final List m_index;
     private final File m_contentsDir;
 
@@ -48,12 +47,12 @@
      * @throws DeploymentException Thrown if the disk contents do not resemble a valid deployment package.
      * @throws IOException Thrown if there was a problem reading the resources from disk.
      */
-    public FileDeploymentPackage(File index, File packageDir, BundleContext bundleContext) throws DeploymentException, IOException {
-        this(ExplodingOutputtingInputStream.readIndex(index), packageDir, bundleContext);
+    public FileDeploymentPackage(File index, File packageDir, BundleContext bundleContext, DeploymentAdminImpl deploymentAdmin) throws DeploymentException, IOException {
+        this(ExplodingOutputtingInputStream.readIndex(index), packageDir, bundleContext, deploymentAdmin);
     }
 
-    private FileDeploymentPackage(List index, File packageDir, BundleContext bundleContext) throws DeploymentException, IOException {
-        super(new Manifest(new GZIPInputStream(new FileInputStream(new File(packageDir, (String) index.remove(0))))), bundleContext);
+    private FileDeploymentPackage(List index, File packageDir, BundleContext bundleContext, DeploymentAdminImpl deploymentAdmin) throws DeploymentException, IOException {
+        super(new Manifest(new GZIPInputStream(new FileInputStream(new File(packageDir, (String) index.remove(0))))), bundleContext, deploymentAdmin);
         m_index = index;
         m_contentsDir = packageDir;
     }
@@ -95,5 +94,4 @@
     public AbstractInfo getNextEntry() throws IOException {
         throw new UnsupportedOperationException("Not implemented for file-based deployment package");
     }
-
 }
\ No newline at end of file
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java
index 4c5b851..887628b 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java
@@ -32,8 +32,7 @@
 /**
  * This class represents a deployment package that is read from a jar stream.
  */
-class StreamDeploymentPackage extends AbstractDeploymentPackage {
-
+public class StreamDeploymentPackage extends AbstractDeploymentPackage {
     private final JarInputStream m_input;
     private final List m_names = new ArrayList();
 
@@ -44,8 +43,8 @@
      * @param bundleContext The bundle context.
      * @throws DeploymentException If it was not possible to read a valid deployment package from the specified stream.
      */
-    public StreamDeploymentPackage(JarInputStream input, BundleContext bundleContext) throws DeploymentException {
-        super(input.getManifest(), bundleContext);
+    public StreamDeploymentPackage(JarInputStream input, BundleContext bundleContext, DeploymentAdminImpl deploymentAdmin) throws DeploymentException {
+        super(input.getManifest(), bundleContext, deploymentAdmin);
         m_input = input;
     }
 
@@ -94,5 +93,4 @@
     public InputStream getCurrentEntryStream() {
         return new NonCloseableStream(m_input);
     }
-
 }
\ No newline at end of file
diff --git a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java
index f94eb30..8bc7056 100644
--- a/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java
+++ b/deploymentadmin/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java
@@ -21,7 +21,6 @@
 import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
 import org.apache.felix.deploymentadmin.BundleInfoImpl;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.FrameworkListener;
@@ -32,21 +31,19 @@
  * Command that starts all bundles described in the source deployment package of a deployment session.
  */
 public class StartBundleCommand extends Command {
-
     private final RefreshPackagesMonitor m_refreshMonitor = new RefreshPackagesMonitor();
     private static final int REFRESH_TIMEOUT = 10000;
 
     public void execute(DeploymentSessionImpl session) {
         AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
-        BundleContext context = session.getBundleContext();
         PackageAdmin packageAdmin = session.getPackageAdmin();
         RefreshPackagesListener listener = new RefreshPackagesListener();
         LogService log = session.getLog();
 
-        context.addFrameworkListener(listener);
+        session.getBundleContext().addFrameworkListener(listener);
         packageAdmin.refreshPackages(null);
         m_refreshMonitor.waitForRefresh();
-        context.removeFrameworkListener(listener);
+        session.getBundleContext().removeFrameworkListener(listener);
 
         // start source bundles
         BundleInfoImpl[] bundleInfos = source.getOrderedBundleInfos();
@@ -76,7 +73,6 @@
     private class RefreshPackagesListener implements FrameworkListener {
         public void frameworkEvent(FrameworkEvent event) {
             if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
-                // TODO: m_log.log(LogService.LOG_INFO, "Packages refreshed event received");
                 m_refreshMonitor.proceed();
             }
         }
@@ -97,20 +93,16 @@
          */
         public synchronized void waitForRefresh() {
             if (!m_alreadyNotified) {
-             // TODO: m_log.log(LogService.LOG_DEBUG, "wait for Packages refreshed event");
                 try {
                     wait(REFRESH_TIMEOUT);
                 }
                 catch (InterruptedException ie) {
-                 // TODO: m_log.log(LogService.LOG_INFO, "interrupted while waiting for packages refreshed event", ie);
                 }
                 finally {
-                    // just reset the misted notification variable, this Monitor object might be reused.
                     m_alreadyNotified = false;
                 }
             }
             else {
-                // TODO: m_log.log(LogService.LOG_DEBUG, "won't wait for Packages refreshed event, event is already received");
                 // just reset the misted notification variable, this Monitor object might be reused.
                 m_alreadyNotified = false;
             }
@@ -125,5 +117,4 @@
             notifyAll();
         }
     }
-
 }