com.adito.extensions.store.ExtensionStore.java Source code

Java tutorial

Introduction

Here is the source code for com.adito.extensions.store.ExtensionStore.java

Source

/*
*  Adito
*
*  Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package com.adito.extensions.store;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForward;
import org.jdom.JDOMException;

import com.adito.boot.Context;
import com.adito.boot.ContextHolder;
import com.adito.boot.PropertyList;
import com.adito.boot.RepositoryFactory;
import com.adito.boot.RepositoryStore;
import com.adito.boot.SystemProperties;
import com.adito.boot.Util;
import com.adito.boot.VersionInfo;
import com.adito.core.BundleActionMessage;
import com.adito.core.CoreAttributeConstants;
import com.adito.core.CoreEvent;
import com.adito.core.CoreEventConstants;
import com.adito.core.CoreMessageResources;
import com.adito.core.CoreServlet;
import com.adito.core.CoreUtil;
import com.adito.core.GlobalWarning;
import com.adito.core.GlobalWarningManager;
import com.adito.core.LicenseAgreement;
import com.adito.core.GlobalWarning.DismissType;
import com.adito.extensions.ExtensionBundle;
import com.adito.extensions.ExtensionDescriptor;
import com.adito.extensions.ExtensionException;
import com.adito.extensions.ExtensionInstaller;
import com.adito.extensions.ExtensionType;
import com.adito.extensions.ExtensionBundle.ExtensionBundleStatus;
import com.adito.setup.LicenseAgreementCallback;
import com.adito.tasks.Task;
import com.adito.tasks.TaskHttpServletRequest;
import com.adito.tasks.TaskInputStream;
import com.adito.tasks.TaskProgressBar;
import com.adito.util.ZipExtract;

/**
 * Manages all aspects of <i>Extensions</i> including loading, starting,
 * activating, disabling, installing, remove and updating.
 * <p>
 * This class also manages the interaction with the <i>Adito Extension Store</i>,
 * including update checking and installing from the store.
 */
public class ExtensionStore {

    public static final String HTTP_3SP_COM_APPSTORE = "http://www.aditoproject.org/appstore/";
    private static final Log log = LogFactory.getLog(ExtensionStore.class);
    private static final String DIRS_TO_REMOVE = "dirsToRemove";
    private static String currentEdition = "Community Edition";

    public static final String UPDATEABLE = "Updateable";

    /**
     * Name of store in repository for extension bundle archives
     */
    public static final String ARCHIVE_STORE = "archives";

    /**
     * 'Installed' category in extension manager
     */
    public static final String INSTALLED_CATEGORY = "Installed";

    /**
     * The extension bundle id for the Agent.
     */
    public static final String AGENT_EXTENSION_BUNDLE_ID = "adito-agent";

    /**
     * Preferences node for storing current extension version and other
     * extension related details
     */
    public static final Preferences PREFS = ContextHolder.getContext().getPreferences().node("extensions");

    /**
     * Preferences node with the extensions node for storing extension store
     * related details.
     */
    public static final Preferences STORE_PREF = PREFS.node("store");

    /**
     * Preferences node with the extensions node for storing extension version
     * details.
     */
    public static final Preferences VERSION_PREFS = PREFS.node("versions");

    /**
     * Extension store connect timeout
     */
    public static final int CONNECT_TIMEOUT = 30000;

    /**
     * Extension store read timeout
     */
    public static final int READ_TIMEOUT = 30000;

    // Private instance variables

    private File basedir;
    private Map<String, ExtensionBundle> extensionBundles;
    private List<ExtensionBundle> extensionBundlesList;
    private List<ExtensionInstaller> extensionBundleInstallList;
    private ExtensionStoreDescriptor downloadableExtensions;
    private Calendar downloadableExtensionsLastUpdated;
    private boolean repositoryBacked;
    private static ExtensionStore instance;
    //private UpdateChecker updateChecker;

    /**
     * Get an instance of the extension store.
     * 
     * @return instance
     */
    public static ExtensionStore getInstance() {
        if (instance == null) {
            instance = new ExtensionStore();
        }
        return instance;
    }

    /**
     * Get the update checker that checks for update to the core, extensions and
     * loads the RSS feeds
     * 
     * @return update checker
     */
    //public UpdateChecker getUpdateChecker() {
    //   return updateChecker;
    //}

    /**
     * Get the directory where expanded extensions are stored.
     * 
     * @return the extension store directory
     */
    public File getExtensionStoreDirectory() {
        return basedir;
    }

    /**
     * Get if the extension store is 'Repository Backed'. See class description
     * for details.
     * 
     * @return true if it is repository backed
     */
    public boolean isRepositoryBacked() {
        return repositoryBacked;
    }

    /**
     * Initialise the extension store.
     * 
     * @param basedir
     * @throws IOException
     */
    public void init(File basedir) throws IOException {

        //updateChecker = new UpdateChecker();

        // Get if the application store comes from the repository
        repositoryBacked = "true".equals(SystemProperties.get("adito.extensions.repositoryBacked", "true"));

        this.basedir = basedir;

        extensionBundles = new HashMap<String, ExtensionBundle>();
        extensionBundlesList = new ArrayList<ExtensionBundle>();
        extensionBundleInstallList = new ArrayList<ExtensionInstaller>();

        if (isRepositoryBacked()) {
            initialiseRepository();
        }

        try {
            loadAll();
            // TODO display errors to use somehow
        } catch (Exception e) {
            log.error("Failed extract extension bundles from repository.", e);
        }

        /*
         * A lot of plugins were made incompatible at 0.2.10. Here we make sure
         * any incompatible extensions are disabled
         */
        VersionInfo.Version sslxVersion = ContextHolder.getContext().getVersion();
        if (sslxVersion.getMajor() == 0 && sslxVersion.getMinor() == 2 && sslxVersion.getBuild() == 10) {
            StringBuffer buf = new StringBuffer();
            for (ExtensionBundle bundle : extensionBundlesList) {
                if (bundle.getRequiredHostVersion() == null
                        || bundle.getRequiredHostVersion().compareTo(sslxVersion) < 0) {
                    log.warn("Extension " + bundle.getId() + " has a required host version of "
                            + bundle.getRequiredHostVersion() + " where as " + "this version is " + sslxVersion
                            + ". This plugin will be disabled.");
                    ExtensionStoreStatusManager.systemDisableExtension(bundle.getId());
                    if (buf.length() > 0) {
                        buf.append(",");
                    }
                    buf.append(bundle.getName());
                }
            }
            if (buf.length() > 0) {
                GlobalWarningManager.getInstance()
                        .addMultipleGlobalWarning(new GlobalWarning(GlobalWarning.MANAGEMENT_USERS,
                                new BundleActionMessage("extensions", "startup.disabledExtensions", buf.toString()),
                                DismissType.DISMISS_FOR_USER));
            }
        }

        /*
         * First remove any plugins whose uninstallation may have been deferred
         * until restart
         */
        if (!isRepositoryBacked()) {

            String dirsToRemove = STORE_PREF.get(DIRS_TO_REMOVE, "");
            if (!dirsToRemove.equals("")) {
                StringTokenizer t = new StringTokenizer(dirsToRemove, ",");
                while (t.hasMoreTokens()) {
                    File dir = new File(t.nextToken());
                    if (dir.exists()) {
                        if (log.isInfoEnabled())
                            log.info("Removing extension " + dir.getAbsolutePath());
                        Util.delTree(dir);
                    }
                }
                STORE_PREF.remove(DIRS_TO_REMOVE);
            }

            /*
             * Check for extension updates
             */
            File updatedExtensionsDir = getUpdatedExtensionsDirectory();
            File[] extensions = updatedExtensionsDir.listFiles();
            if (extensions != null) {
                for (int i = 0; i < extensions.length; i++) {
                    File destDir = new File(ContextHolder.getContext().getApplicationDirectory(),
                            extensions[i].getName());
                    if (destDir.exists()) {
                        if (log.isInfoEnabled())
                            log.info("Removing extension " + destDir.getAbsolutePath());
                        if (!Util.delTree(destDir)) {
                            throw new IOException("Failed to remove old extension " + destDir.getAbsolutePath());
                        }
                    }
                    if (log.isInfoEnabled())
                        log.info("Moving " + extensions[i].getAbsolutePath() + " to " + destDir.getAbsolutePath());
                    if (!extensions[i].renameTo(destDir)) {
                        throw new IOException("Failed to rename extension " + extensions[i].getAbsolutePath()
                                + " to " + destDir.getAbsolutePath());
                    }
                }
            }
        }

        // Add any additional class path elements
        addAdditionalClasspath();
        addAdditionalWebResource();

        // Check for any mandatory updates
        //try {
        //    updateChecker.initialise();
        //} catch (Exception e) {
        /*
         * There is no need to prevent start up if we fail to get the
         * available versions.
         */
        //    log.error("Failed to check for any extension updates.", e);
        //}
    }

    /**
     * @return the available extension bundles
     */
    @SuppressWarnings("unchecked")
    public List<ExtensionBundle> getAllAvailableExtensionBundles() {
        List<ExtensionBundle> all = new ArrayList<ExtensionBundle>(extensionBundlesList);
        try {
            ExtensionStoreDescriptor descriptor = getDownloadableExtensionStoreDescriptor(
                    downloadableExtensions != null, getWorkingVersion());
            if (descriptor != null && descriptor.getExtensionBundles() != null) {
                for (Iterator itr = descriptor.getExtensionBundles().iterator(); itr.hasNext();) {
                    ExtensionBundle bundle = (ExtensionBundle) itr.next();
                    // If the app is already installed, remove dont include it
                    // in the list
                    if (!extensionBundles.containsKey(bundle.getId())) {
                        all.add(bundle);
                    }
                }
            }
        } catch (Exception e) {
            log.error("Failed to get downloadable extensions.", e);
        }
        Collections.sort(all);
        return all;
    }

    /**
      * Get all the available extension bundles fro a given category.
      * 
     * @param category 
     * @return the available extension bundles
     */
    @SuppressWarnings("unchecked")
    public List<ExtensionBundle> getAllAvailableExtensionBundles(String category) {
        // just get the installed ones.
        if (category.equals(INSTALLED_CATEGORY)) {
            return extensionBundlesList;
        }

        List<ExtensionBundle> all = new ArrayList<ExtensionBundle>();
        if (category.equals(UPDATEABLE)) {
            for (ExtensionBundle bundle : extensionBundles.values()) {
                // add all the updateable extensions
                if (bundle.isUpdateable()) {
                    all.add(bundle);
                }
            }
        }

        try {
            ExtensionStoreDescriptor descriptor = getDownloadableExtensionStoreDescriptor(
                    downloadableExtensions != null, getWorkingVersion());
            if (descriptor != null && descriptor.getExtensionBundles() != null) {
                for (Iterator itr = descriptor.getExtensionBundles().iterator(); itr.hasNext();) {
                    ExtensionBundle bundle = (ExtensionBundle) itr.next();
                    // If the app is already installed, remove dont include it
                    // in the list
                    if (!extensionBundles.containsKey(bundle.getId()) && bundle.getCategory().equals(category)) {
                        all.add(bundle);
                    }
                }
            }
        } catch (Exception e) {
            log.error("Failed to get downloadable extensions.", e);
        }
        Collections.sort(all);
        return all;
    }

    /**
     * @param id
     * @param version 
     * @return URLConnection
     * @throws IOException
     */
    public URLConnection downloadExtension(String id, String version) throws IOException {
        URL downloadURL = getDownloadURL(id, version);
        if (downloadURL != null) {
            if (log.isInfoEnabled())
                log.info("Downloading extension from " + downloadURL.toExternalForm());
            URLConnection con = downloadURL.openConnection();
            con.setConnectTimeout(CONNECT_TIMEOUT);
            con.setReadTimeout(READ_TIMEOUT);
            con.connect();
            return con;
        } else {
            throw new IOException("No valid download location for " + id);
        }
    }

    /**
     * Start all extensions bundles
     * 
     * @throws ExtensionException any error starting a bundle. If multiple
     *         extensions are started then only the first exception thrown by
     *         the bundle will be thrown from this method, an attempt will be
     *         made to start all other extensions bundles.
     */
    public void start() throws ExtensionException {
        if (log.isInfoEnabled())
            log.info("Starting extension store. Extensions will start in the following order .. ");
        Collections.sort(extensionBundlesList, new BundleComparator());
        for (ExtensionBundle bundle : extensionBundlesList) {
            log.info("    " + bundle.getId() + " (" + bundle.getOrder() + ")");
        }

        for (ExtensionBundle bundle : extensionBundlesList) {
            boolean setupMode = ContextHolder.getContext().isSetupMode();
            if (!setupMode || (setupMode && bundle.isStartOnSetupMode())) {
                ContextHolder.getContext().getBootProgressMonitor().updateMessage("Starting " + bundle.getName());
                ContextHolder.getContext().getBootProgressMonitor().updateProgress((int) (30
                        + (10 * ((float) extensionBundlesList.indexOf(bundle) / extensionBundlesList.size()))));

                // Start the bundle
                try {
                    if (bundle.getStatus() == ExtensionBundleStatus.ENABLED) {
                        bundle.start();
                    }
                } catch (Throwable t) {
                    /*
                     * Catch throwable to prevent bad extensions from interferring
                     * with the core (e.g. NoClassDefFoundError,
                     * ClassNotFoundException)
                     */
                    log.error("Failed to start extension bundle.", t);
                }
            }
        }

        /*
         * First check which extensions should have their installers run
         */
        checkExtensionsForInstallation();

        /*
         * Now run the installer for the start phase
         */
        performInstalls(ExtensionInstaller.ON_START);

    }

    /**
     * Stop all extensions bundles
     * 
     * @throws ExtensionException any error stopping a bundle. If multiple
     *         extensions are started then only the first exception thrown by
     *         the bundle will be thrown from this method, an attempt will be
     *         made to start all other extensions bundles.
     */
    public void stop() throws ExtensionException {
        if (log.isInfoEnabled())
            log.info("Stopping extensions");

        // ensure all threads are finished.
        //updateChecker.end();

        // Stop extensions in the reverse order they were started
        if (extensionBundlesList != null) {
            Collections.reverse(extensionBundlesList);

            ExtensionException ee = null;
            for (ExtensionBundle bundle : extensionBundlesList) {
                try {
                    bundle.stop();
                } catch (ExtensionException e) {
                    if (ee == null) {
                        ee = e;
                    }
                    log.error("Failed to stop extension bundle.", ee);
                }
            }
            if (ee != null) {
                throw ee;
            }
        }
    }

    /**
     * Activate all extensions bundles
     * 
     * @throws ExtensionException any error activating a bundle. If multiple
     *         extensions are started then only the first exception thrown by
     *         the bundle will be thrown from this method, an attempt will be
     *         made to start all other extensions bundles.
     */
    public void activate() throws ExtensionException {
        if (log.isInfoEnabled())
            log.info("Activating extension store.");

        StringBuffer buf = new StringBuffer();
        for (ExtensionBundle bundle : extensionBundlesList) {
            try {
                if (bundle.getStatus() == ExtensionBundleStatus.STARTED) {

                    ContextHolder.getContext().getBootProgressMonitor()
                            .updateMessage("Activating " + bundle.getName());
                    ContextHolder.getContext().getBootProgressMonitor().updateProgress((int) (65
                            + (10 * ((float) extensionBundlesList.indexOf(bundle) / extensionBundlesList.size()))));

                    bundle.activate();

                    if (buf.length() != 0)
                        buf.append(",");
                    buf.append(bundle.getId());
                }
            } catch (Throwable t) {
                /*
                 * Catch throwable to prevent bad extensions from interferring
                 * with the core (e.g. NoClassDefFoundError,
                 * ClassNotFoundException)
                 */
                log.error("Failed to activate extension bundle.", t);
            }
        }

        /*
         * Remove the versions of any extensions that no longer exist
         */
        String[] k;
        try {
            k = VERSION_PREFS.keys();
            for (int i = 0; i < k.length; i++) {
                if (!isExtensionBundleLoaded(k[i])) {
                    VERSION_PREFS.remove(k[i]);
                }
            }
        } catch (BackingStoreException e) {
            log.warn("Could not clean up extension versions preferences node.", e);
        }

        // Start watching for version updates and RSS updates (if enabled)
        //updateChecker.start();

        /*
         * Now run the installer for the start phase
         */
        performInstalls(ExtensionInstaller.ON_ACTIVATE);

        /**
         * If activat plugins has changed since the last full activation
         * then clear out the compiled JSP files
         */
        if (!buf.toString().equals(PREFS.get("lastActivatedPlugins", ""))) {
            Util.delTree(new File(ContextHolder.getContext().getTempDirectory(), "org"));
        }
        PREFS.put("lastActivatedPlugins", buf.toString());

        /* Flush these preferences now incase the server is terminated before 
         * it gets a chance to write the preferences. When running in a development,
         * its possible for the server to get confused about when it should
         * clear out the temporary temporary directory. 
         */
        try {
            PREFS.flush();
        } catch (BackingStoreException bse) {
        }
    }

    /**
     * reset
     */
    public void resetExtensionStoreUpdate() {
        downloadableExtensions = null;
    }

    /**
     * @param connect
     * @return ExtensionStoreDescriptor
     * @throws IOException
     * @throws JDOMException
     */
    public ExtensionStoreDescriptor getDownloadableExtensionStoreDescriptor(boolean connect)
            throws IOException, JDOMException {
        return getDownloadableExtensionStoreDescriptor(connect, getWorkingVersion());
    }

    /**
     * @param connect
     * @param version
     * @return ExtensionStoreDescriptor
     * @throws IOException
     * @throws JDOMException
     */
    public ExtensionStoreDescriptor getDownloadableExtensionStoreDescriptor(boolean connect,
            VersionInfo.Version version) throws IOException, JDOMException {
        if (downloadableExtensions != null && downloadableExtensionsLastUpdated != null) {
            Calendar calendar = ((Calendar) downloadableExtensionsLastUpdated.clone());
            calendar.add(Calendar.DAY_OF_MONTH, 1);
            if (new GregorianCalendar().after(calendar)) {
                if (log.isInfoEnabled())
                    log.info("Downloadable extensions are out of date, will contact the update site again.");
                downloadableExtensions = null;
            }
        }

        if (downloadableExtensions == null && connect) {
            URL storeURL = getStoreDownloadURL(ExtensionStore.HTTP_3SP_COM_APPSTORE, version);
            if (storeURL != null) {
                if (log.isInfoEnabled())
                    log.info("Loading extension store descriptor from " + storeURL.getHost());

                downloadableExtensions = new ExtensionStoreDescriptor(storeURL);
                downloadableExtensionsLastUpdated = new GregorianCalendar();

                for (ExtensionBundle extensionBundle : downloadableExtensions.getExtensionBundles()) {
                    try {
                        ExtensionBundle installedApp = getExtensionBundle(extensionBundle.getId());
                        if (!installedApp.isDevExtension()
                                && installedApp.getVersion().compareTo(extensionBundle.getVersion()) < 0) {
                            if (log.isInfoEnabled())
                                log.info("Update found for extenions " + extensionBundle.getId());
                            installedApp.setType(ExtensionBundle.TYPE_UPDATEABLE);
                            installedApp.setUpdateVersion(extensionBundle.getVersion());
                            installedApp.setChanges(extensionBundle.getChanges());
                        }
                    } catch (Exception e) {
                        if (log.isInfoEnabled())
                            log.info("Extension " + extensionBundle.getId() + " is not installed.");
                    }
                }

                if (log.isInfoEnabled())
                    log.info("Extension store descriptor loaded from " + storeURL.getHost());
            }
        }
        return downloadableExtensions;
    }

    void performInstalls(String phase) {
        for (ExtensionInstaller installer : extensionBundleInstallList) {
            try {
                if (log.isInfoEnabled()) {
                    log.info("Performing installer for " + installer.getBundle().getName() + " phase " + phase);
                }
                installer.doInstall(phase);
            } catch (Exception e) {
                log.warn("Installer for " + installer.getBundle().getName() + " phase " + phase + " failed.", e);
            }
        }
    }

    void checkExtensionsForInstallation() {
        extensionBundleInstallList.clear();
        PropertyList forceInstalls = new PropertyList(SystemProperties.get("adito.forceInstallers", ""));
        for (ExtensionBundle bundle : extensionBundlesList) {
            if (bundle.getInstaller().getOpCount() > 0 && bundle.getStatus().isStartedOrActivated()) {
                String ver = VERSION_PREFS.get(bundle.getId(), "");
                boolean force = forceInstalls.contains(bundle.getId());
                if (force || ver.equals("") || !ver.equals(bundle.getVersion().toString())) {
                    if (force) {
                        log.info("Will run installer for " + bundle.getId()
                                + " because it has been forced by the adito.foreceInstallers property.");
                    } else if (ver.equals("")) {
                        log.info("Will run installer for " + bundle.getId() + " because this is its first install");
                    } else {
                        log.info("Will run installer for " + bundle.getId() + " because the last installed version "
                                + ver + " has been upgraded to " + bundle.getVersion().toString());
                    }
                    extensionBundleInstallList.add(bundle.getInstaller());
                }
            }
        }
    }

    private static URL getDownloadURL(String id, String version) {
        try {
            String location = SystemProperties.get("adito.downloadableApplicationStore.location",
                    ExtensionStore.HTTP_3SP_COM_APPSTORE);
            location += Util.urlEncode(id) + "/" + Util.urlEncode(version) + "/" + Util.urlEncode(id) + ".zip";
            return new URL(location);
        } catch (MalformedURLException murle) {
            try {
                String path = SystemProperties.get("adito.downloadableApplications.location");
                path = path.replaceAll("\\$\\{id\\}", id);
                path = path.replaceAll("\\$\\{version\\}", version);
                return new File(path).toURL();
            } catch (MalformedURLException e) {
                log.error(
                        "Invalid downloadable extension location specified in system property adito.downloadableApplicationStore.location, '"
                                + SystemProperties.get("adito.downloadableApplicationStore.location")
                                + "'. Must be either a URL or the file path of the store descriptor file.");
            }
        }
        return null;
    }

    public static URL getStoreDownloadURL(String appStoreLocation, VersionInfo.Version version) {
        try {
            String location = SystemProperties.get("adito.downloadableApplicationStore.location", appStoreLocation);
            location += "core/" + Util.urlEncode(version.toString()) + "/store.xml";
            return new URL(location);
        } catch (MalformedURLException murle) {
            try {
                return new File(SystemProperties.get("adito.downloadableApplicationStore.location")).toURL();
            } catch (MalformedURLException e) {
                log.error(
                        "Invalid downloadable extension store location specified in system property adito.downloadableApplicationStore.location, '"
                                + SystemProperties.get("adito.downloadableApplicationStore.location")
                                + "'. Must be either a URL or the file path of the store descriptor file.");
            }
        }
        return null;
    }

    /**
     * 
     */
    public void deregisterApplicationPermissions() {
        Util.toDo("Deregister application permissions");
    }

    private synchronized void reloadAll() throws Exception {
        CoreMessageResources resources = CoreServlet.getServlet().getExtensionStoreResources();
        for (ExtensionBundle extensionBundle : extensionBundles.values()) {
            for (ExtensionDescriptor descriptor : extensionBundle) {
                Collection<String> toRemove = new ArrayList<String>();

                for (Iterator itr = resources.keys(); itr.hasNext();) {
                    String key = (String) itr.next();
                    if (key.startsWith("application." + descriptor.getId() + ".")) {
                        toRemove.add(key);
                    }
                }

                for (Iterator itr = toRemove.iterator(); itr.hasNext();) {
                    resources.removeKey((String) itr.next());
                }
            }
        }

        extensionBundles.clear();
        extensionBundlesList.clear();

        loadAll();
    }

    @SuppressWarnings("unchecked")
    private void loadAll() throws Exception {

        if (log.isInfoEnabled())
            log.info("Loading applications");

        if (!basedir.exists()) {
            basedir.mkdirs();
        }

        // Load dev extensions first
        loadDevExtensions();

        File[] files = basedir.listFiles();
        for (int index = 0; index < files.length; index++) {
            try {
                loadDir(files[index]);
            } catch (Exception e) {
                log.error("Failed to load " + files[index].getName(), e);
            }
        }

        String descriptors = SystemProperties.get("adito.additionalDescriptors", "");
        // Load any additional descriptors
        for (StringTokenizer tokenizer = new StringTokenizer(descriptors, ","); tokenizer.hasMoreTokens();) {
            File file = new File(tokenizer.nextToken());
            if (file.exists()) {
                try {
                    loadBundle(file, false);
                } catch (Exception e) {
                    log.error("Failed to load " + file.getAbsolutePath(), e);
                }
            }
        }

        Collections.sort(extensionBundlesList);
    }

    private void loadDir(File dir) throws ExtensionException {
        if (dir.isDirectory()) {
            File[] descriptors = dir.listFiles(new FilenameFilter() {
                public boolean accept(File f, String filename) {
                    return filename.equals("application.xml") || filename.equals("extension.xml");
                }
            });

            if (descriptors.length == 0) {
                log.warn("Extension folder " + dir.getName()
                        + " found with no extension.xml (or the deprecated application.xml)");
                return;
            } else if (descriptors.length > 1) {
                // Should never happen if its case sensitive
                log.warn("Extension folder " + dir.getName()
                        + " found with too many extension.xml (or deprecated application.xml) files. Please remove one. This extensions will be ignored.");
                return;
            }
            if (log.isInfoEnabled())
                log.info("Found application bundle " + dir.getName());

            if (descriptors[0].getName().equals("application.xml")) {
                log.warn("DEPRECATED. Application descriptor file " + descriptors[0]
                        + "  is no longer used, please use extension.xml instead.");
            }
            loadBundle(descriptors[0], false);
        }
    }

    private void loadBundle(File descriptor, boolean devExtension) throws ExtensionException {
        ExtensionBundle bundle = new ExtensionBundle(descriptor, devExtension);
        loadBundle(bundle);
    }

    private void loadBundle(ExtensionBundle bundle) throws ExtensionException {

        bundle.load();
        ExtensionBundle oldBundle = (ExtensionBundle) extensionBundles.get(bundle.getId());

        if (oldBundle != null && oldBundle.isDevExtension()) {
            throw new ExtensionException(ExtensionException.CANNOT_REPLACE_DEV_EXTENSION, bundle.getId());
        }

        bundle.setCategory(ExtensionStore.INSTALLED_CATEGORY);
        try {
            ExtensionBundleStatus extensionStatus = ExtensionStoreStatusManager.getExtensionStatus(bundle.getId());
            bundle.setStatus(extensionStatus);
        } catch (IOException ioe) {
            throw new ExtensionException(ExtensionException.INTERNAL_ERROR, ioe, "Failed to add bundle.");
        }

        for (ExtensionDescriptor descriptor : bundle) {
            if (log.isInfoEnabled())
                log.info("Extension " + descriptor.getName() + " has been loaded");
        }
        extensionBundlesList.remove(oldBundle);
        extensionBundles.put(bundle.getId(), bundle);
        extensionBundlesList.add(bundle);
    }

    /**
     * @param bundleId
     * @throws ExtensionException
     * @throws IOException
     */
    public void systemDisableExtension(String bundleId) throws ExtensionException, IOException {
        ExtensionBundle extensionBundle = getExtensionBundle(bundleId);
        extensionBundle.setStatus(ExtensionBundle.ExtensionBundleStatus.SYSTEM_DISABLED);
        ExtensionStoreStatusManager.systemDisableExtension(bundleId);
    }

    /**
     * @param bundleId
     * @throws Exception
     */
    public void disableExtension(String bundleId) throws Exception {
        ExtensionBundle extensionBundle = getExtensionBundle(bundleId);
        extensionBundle.setStatus(ExtensionBundle.ExtensionBundleStatus.DISABLED);
        ExtensionStoreStatusManager.disableExtension(bundleId);
        if (extensionBundle.isContainsPlugin()) {
            extensionBundle.setType(ExtensionBundle.TYPE_PENDING_STATE_CHANGE);
        }
    }

    /**
     * @param bundleId
     * @throws Exception
     */
    public void enableExtension(String bundleId) throws Exception {
        ExtensionBundle extensionBundle = getExtensionBundle(bundleId);
        extensionBundle.setStatus(ExtensionBundle.ExtensionBundleStatus.ENABLED);
        ExtensionStoreStatusManager.enableExtension(bundleId);
        if (extensionBundle.isContainsPlugin()) {
            extensionBundle.setType(ExtensionBundle.TYPE_PENDING_STATE_CHANGE);
        }
    }

    private static void installExtension(ExtensionBundle extensionBundle) throws IOException {
        if (ExtensionBundle.ExtensionBundleStatus.SYSTEM_DISABLED.equals(extensionBundle.getStatus())) {
            ExtensionStoreStatusManager.installExtension(extensionBundle.getId());
            extensionBundle.setStatus(ExtensionBundle.ExtensionBundleStatus.ENABLED);
        }
    }

    /**
     * @return List
     */
    public List<ExtensionBundle> getExtensionBundles() {
        return extensionBundlesList;
    }

    /**
     * @param name
     * @return true if the extension bundle has been loaded
     */
    public boolean isExtensionBundleLoaded(String name) {
        return extensionBundles.containsKey(name);
    }

    /**
     * @return ExtensionDescriptor
     */
    public ExtensionDescriptor getAgentApplication() {
        try {
            ExtensionBundle bundle = getExtensionBundle(AGENT_EXTENSION_BUNDLE_ID);
            return bundle != null ? (ExtensionDescriptor) bundle.getApplicationDescriptor(AGENT_EXTENSION_BUNDLE_ID)
                    : null;
        } catch (Exception e) {
            log.error("Failed to get agent descriptor. Loaded?", e);
            return null;
        }
    }

    /**
     * @param id
     * @return ExtensionDescriptor
     */
    public ExtensionDescriptor getExtensionDescriptor(String id) {
        for (ExtensionBundle bundle : extensionBundlesList) {
            ExtensionDescriptor descriptor = bundle.getApplicationDescriptor(id);
            if (descriptor != null && descriptor instanceof ExtensionDescriptor) {
                return (ExtensionDescriptor) descriptor;
            }
        }
        return null;
    }

    /**
     * Get an extension bundle given its ID.
     * 
     * @param id extension bundle id
     * @return extension bundle
     * @throws ExtensionException if bundle could not be located ({@link ExtensionException#INVALID_EXTENSION}).
     */
    public ExtensionBundle getExtensionBundle(String id) throws ExtensionException {
        if (!extensionBundles.containsKey(id)) {
            throw new ExtensionException(ExtensionException.INVALID_EXTENSION, id);
        }
        return (ExtensionBundle) extensionBundles.get(id);
    }

    /**
     * Reload all
     * 
     * @throws Exception
     */
    public void reload() throws Exception {
        if (log.isInfoEnabled())
            log.info("Reloading all application bundles");
        boolean reconnect = downloadableExtensions != null;
        downloadableExtensions = null;
        downloadableExtensionsLastUpdated = null;
        deregisterApplicationPermissions();

        reloadAll();
        if (reconnect) {
            getDownloadableExtensionStoreDescriptor(true);
        }
    }

    public static VersionInfo.Version getWorkingVersion() {
        VersionInfo.Version version = new VersionInfo.Version(
                SystemProperties.get("adito.forceVersion", ContextHolder.getContext().getVersion().toString()));
        return version;
    }

    /**
     * @param id
     * @throws ExtensionException
     */
    @SuppressWarnings("unchecked")
    public void reload(String id) throws ExtensionException {
        if (log.isInfoEnabled())
            log.info("Reloading application bundle " + id);
        if (isExtensionLoaded(id)) {
            ExtensionBundle bundle = getExtensionBundle(id);
            try {
                bundle.load();
            } catch (ExtensionException ee) {
                log.warn("Failed to reload extension descriptor.", ee);
                extensionBundles.remove(id);
                extensionBundlesList.remove(bundle);
                throw ee;
            }
        } else {
            loadDir(new File(basedir, id));
        }
        Collections.sort(extensionBundlesList);
    }

    /**
     * @return File
     * @throws IOException
     */
    public File getUpdatedExtensionsDirectory() throws IOException {
        File updatedExtensionsDir = new File(ContextHolder.getContext().getConfDirectory(), "updated-extensions");
        if (!updatedExtensionsDir.exists() && !updatedExtensionsDir.mkdirs()) {
            throw new IOException("The extension update directory " + updatedExtensionsDir.getAbsolutePath()
                    + " could not be created.");
        }
        return updatedExtensionsDir;
    }

    /**
     * Remove an extension bundle. The bundle will be stopped if it is started
     * and events will be fired. Global warnings will also be created if the
     * bundle contains any plugins informing the administrator the server must
     * be restarted.
     * 
     * @param bundle bundle to remove
     * @throws Exception on any error
     */
    @SuppressWarnings("unchecked")
    public void removeExtensionBundle(ExtensionBundle bundle) throws Exception {
        if (log.isInfoEnabled())
            log.info("Removing extension bundle " + bundle.getId());
        boolean containsPlugin = bundle.isContainsPlugin();
        try {
            CoreServlet.getServlet()
                    .fireCoreEvent(new CoreEvent(this, CoreEventConstants.REMOVING_EXTENSION, bundle, null,
                            CoreEvent.STATE_SUCCESSFUL)
                                    .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, bundle.getId())
                                    .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_NAME,
                                            bundle.getName()));
            bundle.removeBundle();
            VERSION_PREFS.remove(bundle.getId());
            CoreServlet.getServlet()
                    .fireCoreEvent(new CoreEvent(this, CoreEventConstants.REMOVE_EXTENSION, bundle, null,
                            CoreEvent.STATE_SUCCESSFUL)
                                    .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, bundle.getId())
                                    .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_NAME,
                                            bundle.getName()));
            if (containsPlugin) {
                if (!ExtensionStore.getInstance().isRepositoryBacked()) {
                    if (log.isInfoEnabled())
                        log.info("Extension " + bundle.getId()
                                + " contains plugins, deferring removal until restart.");
                    StringBuffer toRemove = new StringBuffer(STORE_PREF.get(DIRS_TO_REMOVE, ""));
                    if (toRemove.length() > 0) {
                        toRemove.append(",");
                    }
                    toRemove.append(bundle.getBaseDir());
                    STORE_PREF.put(DIRS_TO_REMOVE, toRemove.toString());
                }
            }
            ExtensionStoreStatusManager.removeExtension(bundle.getId());
        } catch (Exception e) {
            CoreServlet.getServlet()
                    .fireCoreEvent(new CoreEvent(this, CoreEventConstants.REMOVE_EXTENSION, null, null, e)
                            .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, bundle.getId())
                            .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_NAME, bundle.getName()));

            throw e;
        } finally {
            if (!containsPlugin) {
                extensionBundles.remove(bundle.getId());
                extensionBundlesList.remove(bundle);
                if (log.isInfoEnabled())
                    log.info("Extension Zip file " + bundle.getId() + ".zip" + " has been deleted.");
            }
            if (ExtensionStore.getInstance().isRepositoryBacked()) {
                RepositoryFactory.getRepository().getStore(ExtensionStore.ARCHIVE_STORE)
                        .removeEntry(bundle.getId() + ".zip");
            }
            Collections.sort(extensionBundlesList);
        }
    }

    /**
     * Determine if an extension is installed given its extension bundle id.
     * 
     * @param id extension bundle id
     * @return true if the extension has been loaded
     */
    public boolean isExtensionLoaded(String id) {
        for (Iterator i = extensionBundlesList.iterator(); i.hasNext();) {
            ExtensionBundle bundle = (ExtensionBundle) i.next();
            if (bundle.containsApplication(id)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Install an extension directly from the Adito Extension Store given its id
     * and an input stream providing the extension bundle archive in zip format.
     * This method will also attempt to install all of the bundles dependencies.
     * 
     * @param id extension bundle id
     * @param in input stream of extension bundle archive (Zip format)
     * @param request request object
      * @param contentLength size of extension bundle if known (-1 when unavailable - used for progress bar)
     * @return ExtensionBundle installed extension bundle
     * @throws ExtensionException on any error
     */
    public ExtensionBundle installExtensionFromStore(final String id, InputStream in, HttpServletRequest request,
            long contentLength) throws ExtensionException {
        ExtensionStoreDescriptor store;
        try {
            // Get the application store descriptor
            store = getDownloadableExtensionStoreDescriptor(true);
            if (store == null) {
                throw new ExtensionException(ExtensionException.INTERNAL_ERROR, "No downloadable applications.");
            }

            ExtensionBundle bundle = store.getApplicationBundle(id);
            if (bundle == null) {
                throw new ExtensionException(ExtensionException.INVALID_EXTENSION, id);
            }

            // Check host version
            Context context = ContextHolder.getContext();
            if (bundle.getRequiredHostVersion() != null
                    && bundle.getRequiredHostVersion().compareTo(context.getVersion()) > 0) {
                throw new ExtensionException(ExtensionException.INSUFFICIENT_ADITO_HOST_VERSION, bundle.getId(),
                        bundle.getRequiredHostVersion().toString());
            }

            // Install all dependencies
            if (bundle.getDependencies() != null) {
                for (String dep : bundle.getDependencies()) {
                    if (isExtensionBundleLoaded(dep)) {
                        ExtensionBundle current = getExtensionBundle(dep);
                        ExtensionBundle available = store.getApplicationBundle(dep);
                        if (available != null) {
                            if (!current.isDevExtension() && isNewerVersionAvailable(available, current)) {
                                if (log.isInfoEnabled())
                                    log.info("Found a dependency (" + dep + "), that needs upgrading. "
                                            + current.getVersion().toString() + " is the current version, "
                                            + available.getVersion().toString() + " is available. Installing now");
                                installExtensionFromStore(current.getId(), available.getVersion().toString(),
                                        request);
                            }
                        }
                    } else {
                        try {
                            if (log.isInfoEnabled())
                                log.info("Found a dependency (" + dep + "), that is not installed. Installing now");
                            installExtensionFromStore(dep, store.getApplicationBundle(dep).getVersion().toString(),
                                    request);
                        } catch (Exception e) {
                            throw new ExtensionException(ExtensionException.INTERNAL_ERROR,
                                    "Failed to install dependency " + dep);
                        }
                    }
                }
            }

            // This action may be wrapped in a task progress monitor
            Task task = (Task) request.getAttribute(TaskHttpServletRequest.ATTR_TASK);
            if (task != null
                    && request.getAttribute(TaskHttpServletRequest.ATTR_TASK_PROGRESS_HANDLED_EXTERNALLY) == null) {
                TaskProgressBar bar = new TaskProgressBar("installExtension", 0, (int) contentLength, 0); // TODO should accept longs
                task.clearProgressBars();
                task.addProgressBar(bar);
                in = new TaskInputStream(bar, in);
                ((TaskInputStream) in).getProgressBar()
                        .setNote(new BundleActionMessage("extensions", "taskProgress.downloadExtension.note", id));
                if (!task.isConfigured())
                    task.configured();
            }

            return installExtension(id, in);
        } catch (IOException jde) {
            throw new ExtensionException(ExtensionException.INTERNAL_ERROR, "Failed to load descriptor.");
        } catch (JDOMException jde) {
            throw new ExtensionException(ExtensionException.FAILED_TO_PARSE_DESCRIPTOR);
        }
    }

    /**
     * Install an extension directly from the Adito Extension Store given its id.
     * This method will also attempt to install all of the bundles dependencies.
     * 
     * @param id extension bundle ID
     * @param version 
     * @param request request object
     * @throws ExtensionException extension errors
     * @throws IOException io errors
     */
    public void installExtensionFromStore(String id, String version, HttpServletRequest request)
            throws IOException, ExtensionException {
        URLConnection connection = downloadExtension(id, version);
        InputStream inputStream = connection.getInputStream();
        installExtensionFromStore(id, inputStream, request, connection.getContentLength());
    }

    /**
     * Install an extension givens its bundle and an input stream providing the
     * extension bundle archive in zip format.
     * 
     * @param id extension bundle ID
     * @param in input stream provided the extension bundle archive (in Zip
     *        format)
     * @return ExtensionBundle
     * @throws IOException io errors
     * @throws ExtensionException extension errors
     */
    public ExtensionBundle installExtension(final String id, InputStream in)
            throws IOException, ExtensionException {
        streamToRepositoryStore(in, id);

        try {
            RepositoryStore repStore = RepositoryFactory.getRepository().getStore(ARCHIVE_STORE);
            ZipExtract.extractZipFile(getExtensionStoreDirectory(), repStore.getEntryInputStream(id + ".zip"));
            reload(id);
            ExtensionBundle newBundle = getExtensionBundle(id);
            installExtension(newBundle);
            fireBundleEvent(CoreEventConstants.INSTALL_EXTENSION, newBundle);
        } catch (IOException e) {
            CoreServlet.getServlet()
                    .fireCoreEvent(new CoreEvent(this, CoreEventConstants.INSTALL_EXTENSION, null, null,
                            CoreEvent.STATE_UNSUCCESSFUL)
                                    .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, id));
            throw e;
        } catch (ExtensionException e) {
            CoreServlet.getServlet()
                    .fireCoreEvent(new CoreEvent(this, CoreEventConstants.INSTALL_EXTENSION, null, null,
                            CoreEvent.STATE_UNSUCCESSFUL)
                                    .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, id));
            throw e;
        }
        return getExtensionBundle(id);
    }

    /**
     * Check the license for a given bundle, adding a page redirect to ask the
     * user to accept a license agreement if required.
     * <p>
     * This should be called just after an extenion is installed either from the
     * Adito Application Store or manually.
     * 
     * @param newBundle newly installed bundle
     * @param request request object
     * @param installedForward the forward to redirect to after license
     *        agreement has been show
     * @throws Exception on any error
     */
    public void licenseCheck(final ExtensionBundle newBundle, HttpServletRequest request,
            final ActionForward installedForward) throws Exception {

        final RepositoryStore repStore = RepositoryFactory.getRepository().getStore(ARCHIVE_STORE);
        // If installing, there may be a license agreement to handle
        File licenseFile = newBundle.getLicenseFile();
        if (licenseFile != null && licenseFile.exists()) {
            LicenseAgreement licenseAgreement = getLicenseAgreement(newBundle, repStore, licenseFile,
                    installedForward);
            CoreUtil.requestLicenseAgreement(request.getSession(), licenseAgreement);
        }
    }

    /**
     * Invoked after an extension has been installed, this method adds a global
     * warning if the bundle contains any plugins indicating a restart is neeed.
     * Also, if the bundle doesn't contain any plugins it will immediately run
     * the 'installer'.
     * 
     * @param newBundle newly installed extension bundle
     * @param request request
     * @throws Exception on any error
     */
    public void postInstallExtension(final ExtensionBundle newBundle, HttpServletRequest request) throws Exception {
        boolean containsPlugin = newBundle.isContainsPlugin();

        if (containsPlugin) {
            GlobalWarningManager.getInstance()
                    .addMultipleGlobalWarning(new GlobalWarning(GlobalWarning.MANAGEMENT_USERS,
                            new BundleActionMessage("extensions",
                                    "extensionStore.message.pluginInstalledRestartRequired"),
                            DismissType.DISMISS_FOR_USER));
            newBundle.setType(ExtensionBundle.TYPE_PENDING_INSTALLATION);
        } else {
            newBundle.start();

            // Installer must be run after start as it may have custom tasks
            if (newBundle.getInstaller() != null) {
                newBundle.getInstaller().doInstall(null);
            }

            newBundle.activate();
        }
    }

    /**
     * Update an extension givens its bundle and an input stream providing the
     * extension bundle archive in zip format.
     * 
     * @param id extension bundle id
     * @param in input stream of extension bundle archive (Zip format)
     * @param request reuqest object
      * @param contentLength content length
     * @return ExtensionBundle newly loaded extension bundle
     * @throws Exception on any error
     */
    public ExtensionBundle updateExtension(String id, InputStream in, HttpServletRequest request,
            long contentLength) throws Exception {
        ExtensionStoreDescriptor store;
        // Get the application store descriptor
        /**
         * LDP - Why does this require the extension store descriptor? Extensions should be 
         * updatable even if the extension store is not available!!!!!!!!
         */
        //      store = getDownloadableExtensionStoreDescriptor(true);
        //      if (store == null) {
        //         throw new ExtensionException(ExtensionException.INTERNAL_ERROR, "No downloadable applications.");
        //      }

        // This action may be wrapped in a task progress monitor
        Task task = (Task) request.getAttribute(TaskHttpServletRequest.ATTR_TASK);
        if (task != null
                && request.getAttribute(TaskHttpServletRequest.ATTR_TASK_PROGRESS_HANDLED_EXTERNALLY) == null) {
            TaskProgressBar bar = new TaskProgressBar("updateExtension", 0, (int) contentLength, 0);
            task.addProgressBar(bar);
            in = new TaskInputStream(bar, in);
            ((TaskInputStream) in).getProgressBar()
                    .setNote(new BundleActionMessage("extensions", "taskProgress.downloadExtension.note", id));
            task.configured();
        }

        ExtensionBundle currentBundle = getExtensionBundle(id);
        if (currentBundle == null) {
            throw new ExtensionException(ExtensionException.INVALID_EXTENSION, id);
        }

        try {
            return updateExtension(currentBundle, in, request);
        } catch (ExtensionException ee) {
            CoreServlet.getServlet().fireCoreEvent(
                    new CoreEvent(this, CoreEventConstants.UPDATE_EXTENSION, null, null, CoreEvent.STATE_SUCCESSFUL)
                            .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, currentBundle.getId())
                            .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_NAME, currentBundle.getName())
                            .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_VERSION,
                                    currentBundle.getVersion().toString()));
            throw ee;
        }
    }

    private ExtensionBundle updateExtension(ExtensionBundle currentBundle, InputStream in,
            HttpServletRequest request) throws Exception {

        // Check host version
        Context context = ContextHolder.getContext();
        if (currentBundle.getRequiredHostVersion() != null
                && currentBundle.getRequiredHostVersion().compareTo(context.getVersion()) > 0) {
            throw new ExtensionException(ExtensionException.INSUFFICIENT_ADITO_HOST_VERSION, currentBundle.getId(),
                    currentBundle.getRequiredHostVersion().toString());
        }

        boolean containsPlugin = currentBundle.isContainsPlugin();

        // Remove Extension Bundle;
        try {
            currentBundle.removeBundle();
            VERSION_PREFS.remove(currentBundle.getId());
        } catch (Exception e) {
            throw e;
        } finally {
            if (!containsPlugin) {
                extensionBundles.remove(currentBundle.getId());
                extensionBundlesList.remove(currentBundle);
                if (log.isInfoEnabled())
                    log.info("Extension Zip file " + currentBundle.getId() + ".zip" + " has been deleted.");
            }
        }

        // Install Extension;
        streamToRepositoryStore(in, currentBundle.getId());
        RepositoryStore repStore = RepositoryFactory.getRepository().getStore(ARCHIVE_STORE);
        ZipExtract.extractZipFile(getExtensionStoreDirectory(),
                repStore.getEntryInputStream(currentBundle.getId() + ".zip"));

        if (containsPlugin) {
            currentBundle.setType(ExtensionBundle.TYPE_PENDING_UPDATE);
            fireBundleEvent(CoreEventConstants.UPDATE_EXTENSION, currentBundle);
            return currentBundle;
        } else {
            reload(currentBundle.getId());
            ExtensionBundle newBundle = getExtensionBundle(currentBundle.getId());
            installExtension(newBundle);
            postInstallExtension(newBundle, request);
            if (newBundle.getStatus().isStartedOrActivated()) {
                newBundle.stop();
                newBundle.start();
                newBundle.activate();
            }
            fireBundleEvent(CoreEventConstants.UPDATE_EXTENSION, newBundle);
            return newBundle;
        }
    }

    /**
     * Set the <i>Edition</i>, i.e. GPL, Community or Enterprise.
     * 
     * @param currentEdition edition
     */
    public static void setCurrentEdition(String currentEdition) {
        ExtensionStore.currentEdition = currentEdition;
    }

    private LicenseAgreement getLicenseAgreement(final ExtensionBundle newBundle, final RepositoryStore repStore,
            final File licenseFile, final ActionForward installedForward) {
        return new LicenseAgreement(newBundle.getName(), licenseFile, new LicenseAgreementCallback() {
            public void licenseAccepted(HttpServletRequest request) {
                // Dont care
            }

            public void licenseRejected(HttpServletRequest request) {
                // Remove the repository entry if it is in
                // use
                if (isRepositoryBacked()) {
                    try {
                        repStore.removeEntry(newBundle.getId() + ".zip");
                    } catch (IOException ex) {
                    }
                }

                // Remove the expanded bundle
                if (newBundle.getBaseDir().exists()) {
                    Util.delTree(newBundle.getBaseDir());
                }

                // Reload the extension store
                try {
                    reload(newBundle.getId());
                } catch (Exception e) {
                    log.error("Failed to reload extension store.");
                }
            }
        }, installedForward);
    }

    private static boolean isNewerVersionAvailable(ExtensionBundle available, ExtensionBundle current) {
        VersionInfo.Version v1 = new VersionInfo.Version(available.getVersion().toString());
        VersionInfo.Version v2 = new VersionInfo.Version(current.getVersion().toString());
        return v1.compareTo(v2) > 0;
    }

    private static void streamToRepositoryStore(InputStream in, String bundleId) throws IOException {
        RepositoryStore repStore = RepositoryFactory.getRepository().getStore(ARCHIVE_STORE);
        OutputStream out = null;
        try {
            out = repStore.getEntryOutputStream(bundleId + ".zip");
            Util.copy(in, out);
        } finally {
            Util.closeStream(in);
            Util.closeStream(out);
        }
    }

    private void fireBundleEvent(int eventType, ExtensionBundle bundle) {
        String extensionType = getExtensionType(bundle);
        CoreServlet.getServlet()
                .fireCoreEvent(new CoreEvent(this, eventType, null, null, CoreEvent.STATE_SUCCESSFUL)
                        .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_ID, bundle.getId())
                        .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_NAME, bundle.getName())
                        .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_VERSION,
                                bundle.getVersion().toString())
                        .addAttribute(CoreAttributeConstants.EVENT_ATTR_EXTENSION_TYPE, extensionType));
    }

    private static String getExtensionType(ExtensionBundle bundle) {
        for (Iterator itr = bundle.iterator(); itr.hasNext();) {
            ExtensionDescriptor descriptor = (ExtensionDescriptor) itr.next();
            if (descriptor.getExtensionType() instanceof ExtensionType) {
                return descriptor.getExtensionType().getType();
            }
        }
        return null;
    }

    private void addAdditionalWebResource() {
        //
        String additionalWebResources = SystemProperties.get("adito.additionalWebResourceDirectories", "");
        if (additionalWebResources != null) {
            StringTokenizer t = new StringTokenizer(additionalWebResources, ",");
            while (t.hasMoreTokens()) {
                try {
                    URL u = null;
                    String dir = t.nextToken();
                    if (dir.endsWith("]")) {
                        int idx = dir.indexOf('[');
                        if (idx != -1) {
                            dir = dir.substring(0, idx);
                            u = new File(dir).getCanonicalFile().toURL();
                            log.warn(
                                    "Associating additional web resource directories with plugins is no longer supported.");
                        }
                    }
                    if (u == null) {
                        u = new File(dir).getCanonicalFile().toURL();
                    }
                    ContextHolder.getContext().addResourceBase(u);
                } catch (Exception e) {
                    log.error("Failed to add additional web resources directory.", e);
                }
            }
        }
    }

    private void addAdditionalClasspath() {

        // Add any additional class path elements
        StringTokenizer t = new StringTokenizer(SystemProperties.get("adito.additionalClasspath", ""), ",");
        while (t.hasMoreTokens()) {
            try {
                String sf = t.nextToken();
                File[] f = null;
                if (sf.endsWith("/*.jar")) {
                    f = new File(sf.substring(0, sf.length() - 6)).listFiles(new FileFilter() {
                        public boolean accept(File pathname) {
                            return pathname.getName().toLowerCase().endsWith(".jar");
                        }
                    });
                } else {
                    f = new File[1];
                    f[0] = new File(sf);
                }
                for (int j = 0; f != null && j < f.length; j++) {
                    if (f[j].exists() && (f[j].isDirectory() || f[j].getName().toLowerCase().endsWith(".jar"))) {
                        URL u = f[j].toURL();
                        ContextHolder.getContext().addContextLoaderURL(u);
                    }
                }
            } catch (MalformedURLException murle) {
                log.warn("Invalid element in additional classpaths");
            }

        }
    }

    private void initialiseRepository() {
        RepositoryStore store = RepositoryFactory.getRepository().getStore("archives");
        // Remove the existing extensions
        if (basedir.exists()) {
            Util.delTree(basedir);
        }

        // Now recreate all extensions from the repository
        basedir.mkdirs();
        String[] archives = store.listEntries();
        for (int i = 0; i < archives.length; i++) {
            if (log.isInfoEnabled()) {
                log.info("Extracting archive " + archives[i]);
            }
            try {
                ZipExtract.extractZipFile(basedir, store.getEntryInputStream(archives[i]));
                if (log.isInfoEnabled()) {
                    log.info("Completed archive extraction for extension " + archives[i]);
                }
            } catch (IOException ex) {
                log.error("Error extracting archive for extension " + archives[i], ex);
                Util.delTree(new File(basedir, archives[i]));
            }
        }
    }

    private void loadDevExtensions() {
        List<String> devExtensions = new ArrayList<String>();
        String extensionList = SystemProperties.get("adito.devExtensions", "");
        StringTokenizer t = new StringTokenizer(extensionList, ",");
        while (t.hasMoreTokens()) {
            String ext = t.nextToken();
            if (ext.equalsIgnoreCase("all")) {
                File f = new File(SystemProperties.get("user.dir")).getParentFile();
                File[] dirs = f.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        File f = new File(pathname, "extensions");
                        return f.exists() && f.isDirectory();
                    }
                });
                for (int i = 0; dirs != null && i < dirs.length; i++) {
                    devExtensions.add(dirs[i].getName());
                }
            } else if (ext.equalsIgnoreCase("enterprise")) {
                File f = new File(SystemProperties.get("user.dir")).getParentFile();
                File[] dirs = f.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        File f = new File(pathname, "extensions");
                        return f.exists() && f.isDirectory()
                                && pathname.getName().indexOf("adito-enterprise-") != -1;
                    }
                });
                for (int i = 0; dirs != null && i < dirs.length; i++) {
                    devExtensions.add(dirs[i].getName());
                }
            } else if (ext.equalsIgnoreCase("community")) {
                File f = new File(SystemProperties.get("user.dir")).getParentFile();
                File[] dirs = f.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        File f = new File(pathname, "extensions");
                        return f.exists() && f.isDirectory()
                                && pathname.getName().indexOf("adito-community-") != -1;
                    }
                });
                for (int i = 0; dirs != null && i < dirs.length; i++) {
                    devExtensions.add(dirs[i].getName());
                }
            } else {
                if (ext.startsWith("!")) {
                    devExtensions.remove(ext.substring(1));
                } else {
                    devExtensions.add(ext);
                }
            }
        }

        for (Iterator it = devExtensions.iterator(); it.hasNext();) {
            String ext = (String) it.next();
            File d = new File(new File(SystemProperties.get("user.dir")).getParentFile(), ext);
            File extensionDir = new File(new File(d, "extensions"), d.getName());
            File extensionDescriptor = new File(extensionDir, "extension.xml");
            if (extensionDescriptor.exists()) {
                try {
                    loadBundle(extensionDescriptor, true);
                } catch (Exception e) {
                    log.error("Failed to load dev extension " + extensionDescriptor.getAbsolutePath(), e);
                }
            }
        }
    }

    class BundleComparator implements Comparator<ExtensionBundle> {
        public int compare(ExtensionBundle o1, ExtensionBundle o2) {
            int i = new Integer(o1.getOrder()).compareTo(new Integer(o2.getOrder()));
            return i == 0 ? o1.getId().compareTo(o2.getId()) : i;
        }
    }

}