org.parosproxy.paros.extension.ExtensionLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.parosproxy.paros.extension.ExtensionLoader.java

Source

/*
 *
 * Paros and its related class files.
 * 
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 * 
 * 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
 * Clarified Artistic License for more details.
 * 
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
// ZAP: 2011/12/14 Support for extension dependencies
// ZAP: 2012/02/18 Rationalised session handling
// ZAP: 2012/03/15 Reflected the change in the name of the method optionsChanged of
// the class OptionsChangedListener. Changed the method destroyAllExtension() to
// save the configurations of the main http panels and save the configuration file.
// ZAP: 2012/04/23 Reverted the changes of the method destroyAllExtension(),
// now the configurations of the main http panels and the configuration file
// are saved in the method Control.shutdown(boolean).
// ZAP: 2012/04/24 Changed the method destroyAllExtension to catch exceptions.
// ZAP: 2012/04/25 Added the type argument and removed unnecessary cast.
// ZAP: 2012/07/23 Removed parameter from View.getSessionDialog call.
// ZAP: 2012/07/29 Issue 43: added sessionScopeChanged event
// ZAP: 2012/08/01 Issue 332: added support for Modes
// ZAP: 2012/11/30 Issue 425: Added tab index to support quick start tab 
// ZAP: 2012/12/27 Added hookPersistentConnectionListener() method.
// ZAP: 2013/01/16 Issue 453: Dynamic loading and unloading of add-ons
// ZAP: 2013/01/25 Added removeExtension(...) method and further helper methods
// to remove listeners, menu items, etc.
// ZAP: 2013/01/25 Refactored hookMenu(). Resolved some Checkstyle issues.
// ZAP: 2013/01/29 Catch Errors thrown by out of date extensions as well as Exceptions
// ZAP: 2013/07/23 Issue 738: Options to hide tabs
// ZAP: 2013/11/16 Issue 807: Error while loading ZAP when Quick Start Tab is closed
// ZAP: 2013/11/16 Issue 845: AbstractPanel added twice to TabbedPanel2 in ExtensionLoader#addTabPanel
// ZAP: 2013/12/03 Issue 934: Handle files on the command line via extension
// ZAP: 2013/12/13 Added support for Full Layout DISPLAY_OPTION_TOP_FULL in the hookView function.
// ZAP: 2014/03/23 Issue 1022: Proxy - Allow to override a proxied message
// ZAP: 2014/03/23 Issue 1090: Do not add pop up menus if target extension is not enabled
// ZAP: 2014/05/20 Issue 1202: Issue with loading addons that did not initialize correctly
// ZAP: 2014/08/14 Catch Exceptions thrown by extensions when stopping them
// ZAP: 2014/08/14 Issue 1309: NullPointerExceptions during a failed uninstallation of an add-on
// ZAP: 2014/10/07 Issue 1357: Hide unused tabs
// ZAP: 2014/10/09 Issue 1359: Added info logging for splash screen
// ZAP: 2014/10/25 Issue 1062: Added scannerhook to be loaded by an active scanner.
// ZAP: 2014/11/11 Issue 1406: Move online menu items to an add-on
// ZAP: 2014/11/21 Reviewed foreach loops and commented startup process for splash screen progress bar
// ZAP: 2015/01/04 Issue 1379: Not all extension's listeners are hooked during add-on installation
// ZAP: 2015/01/19 Remove online menus when removeMenu(View, ExtensionHook) is called.
// ZAP: 2015/01/19 Issue 1510: New Extension.postInit() method to be called once all extensions loaded
// ZAP: 2015/02/09 Issue 1525: Introduce a database interface layer to allow for alternative implementations
// ZAP: 2015/02/10 Issue 1208: Search classes/resources in add-ons declared as dependencies
// ZAP: 2015/04/09 Generify Extension.getExtension(Class) to avoid unnecessary casts
// ZAP: 2015/09/07 Start GUI on EDT
// ZAP: 2016/04/06 Fix layouts' issues
// ZAP: 2016/04/08 Hook ContextDataFactory/ContextPanelFactory 
// ZAP: 2016/05/30 Notification of installation status of the add-ons
// ZAP: 2016/05/30 Issue 2494: ZAP Proxy is not showing the HTTP CONNECT Request in history tab
// ZAP: 2016/08/18 Hook ApiImplementor
// ZAP: 2016/11/23 Call postInit() when starting an extension, startLifeCycle(Extension).
// ZAP: 2017/02/19 Hook/remove extensions' components to/from the main tool bar.
// ZAP: 2017/06/07 Allow to notify of changes in the session's properties (e.g. name, description).
// ZAP: 2017/07/25 Hook HttpSenderListener.
// ZAP: 2017/10/11 Include add-on in extensions' initialisation errors.
// ZAP: 2017/10/31 Add JavaDoc to ExtensionLoader.getExtension(String).

package org.parosproxy.paros.extension;

import java.awt.Component;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.CommandLine;
import org.parosproxy.paros.common.AbstractParam;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.control.Control.Mode;
import org.parosproxy.paros.control.Proxy;
import org.parosproxy.paros.core.proxy.ConnectRequestProxyListener;
import org.parosproxy.paros.core.proxy.OverrideMessageProxyListener;
import org.parosproxy.paros.core.proxy.ProxyListener;
import org.parosproxy.paros.core.scanner.Scanner;
import org.parosproxy.paros.core.scanner.ScannerHook;
import org.parosproxy.paros.db.Database;
import org.parosproxy.paros.db.DatabaseException;
import org.parosproxy.paros.db.DatabaseUnsupportedException;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.model.OptionsParam;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.view.AbstractParamDialog;
import org.parosproxy.paros.view.AbstractParamPanel;
import org.parosproxy.paros.view.MainMenuBar;
import org.parosproxy.paros.view.SiteMapPanel;
import org.parosproxy.paros.view.View;
import org.parosproxy.paros.view.WorkbenchPanel;
import org.zaproxy.zap.PersistentConnectionListener;
import org.zaproxy.zap.control.AddOn;
import org.zaproxy.zap.extension.AddonFilesChangedListener;
import org.zaproxy.zap.extension.api.API;
import org.zaproxy.zap.extension.api.ApiImplementor;
import org.zaproxy.zap.extension.AddOnInstallationStatusListener;
import org.zaproxy.zap.model.ContextDataFactory;
import org.zaproxy.zap.network.HttpSenderListener;
import org.zaproxy.zap.view.ContextPanelFactory;
import org.zaproxy.zap.view.MainToolbarPanel;
import org.zaproxy.zap.view.SiteMapListener;

public class ExtensionLoader {

    private final List<Extension> extensionList = new ArrayList<>();
    private final Map<Class<? extends Extension>, Extension> extensionsMap = new HashMap<>();
    private final Map<Extension, ExtensionHook> extensionHooks = new HashMap<>();
    private Model model = null;

    private View view = null;
    private static final Logger logger = Logger.getLogger(ExtensionLoader.class);

    public ExtensionLoader(Model model, View view) {
        this.model = model;
        this.view = view;
    }

    public void addExtension(Extension extension) {
        extensionList.add(extension);
        extensionsMap.put(extension.getClass(), extension);
    }

    public void destroyAllExtension() {
        for (int i = 0; i < getExtensionCount(); i++) {
            try {
                getExtension(i).destroy();

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }

    }

    public Extension getExtension(int i) {
        return extensionList.get(i);
    }

    /**
     * Gets the {@code Extension} with the given name.
     *
     * @param name the name of the {@code Extension}.
     * @return the {@code Extension} or {@code null} if not found/enabled.
     * @see #getExtension(Class)
     */
    public Extension getExtension(String name) {
        if (name != null) {
            for (int i = 0; i < extensionList.size(); i++) {
                Extension p = getExtension(i);
                if (p.getName().equalsIgnoreCase(name)) {
                    return p;
                }
            }
        }

        return null;
    }

    public Extension getExtensionByClassName(String name) {
        if (name != null) {
            for (int i = 0; i < extensionList.size(); i++) {
                Extension p = getExtension(i);
                if (p.getClass().getName().equals(name)) {
                    return p;
                }
            }
        }

        return null;
    }

    /**
     * Gets the {@code Extension} with the given class.
     *
     * @param clazz the class of the {@code Extension}
     * @return the {@code Extension} or {@code null} if not found/enabled.
     */
    public <T extends Extension> T getExtension(Class<T> clazz) {
        if (clazz != null) {
            Extension extension = extensionsMap.get(clazz);
            if (extension != null) {
                return clazz.cast(extension);
            }
        }
        return null;
    }

    /**
     * Tells whether or not an {@code Extension} with the given
     * {@code extensionName} is enabled.
     *
     * @param extensionName the name of the extension
     * @return {@code true} if the extension is enabled, {@code false}
     * otherwise.
     * @throws IllegalArgumentException if the {@code extensionName} is
     * {@code null}.
     * @see #getExtension(String)
     * @see Extension
     */
    public boolean isExtensionEnabled(String extensionName) {
        if (extensionName == null) {
            throw new IllegalArgumentException("Parameter extensionName must not be null.");
        }

        Extension extension = getExtension(extensionName);
        if (extension == null) {
            return false;
        }

        return extension.isEnabled();
    }

    public int getExtensionCount() {
        return extensionList.size();
    }

    public void hookProxyListener(Proxy proxy) {
        for (ExtensionHook hook : extensionHooks.values()) {
            hookProxyListeners(proxy, hook.getProxyListenerList());
        }
    }

    private static void hookProxyListeners(Proxy proxy, List<ProxyListener> listeners) {
        for (ProxyListener listener : listeners) {
            try {
                if (listener != null) {
                    proxy.addProxyListener(listener);
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private void removeProxyListener(ExtensionHook hook) {
        Proxy proxy = Control.getSingleton().getProxy();
        List<ProxyListener> listenerList = hook.getProxyListenerList();
        for (ProxyListener listener : listenerList) {
            try {
                if (listener != null) {
                    proxy.removeProxyListener(listener);
                }

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    public void hookOverrideMessageProxyListener(Proxy proxy) {
        for (ExtensionHook hook : extensionHooks.values()) {
            List<OverrideMessageProxyListener> listenerList = hook.getOverrideMessageProxyListenerList();
            for (OverrideMessageProxyListener listener : listenerList) {
                try {
                    if (listener != null) {
                        proxy.addOverrideMessageProxyListener(listener);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    private void removeOverrideMessageProxyListener(ExtensionHook hook) {
        Proxy proxy = Control.getSingleton().getProxy();
        List<OverrideMessageProxyListener> listenerList = hook.getOverrideMessageProxyListenerList();
        for (OverrideMessageProxyListener listener : listenerList) {
            try {
                if (listener != null) {
                    proxy.removeOverrideMessageProxyListener(listener);
                }

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    /**
     * Hooks (adds) the {@code ConnectRequestProxyListener}s of the loaded extensions to the given {@code proxy}.
     * <p>
     * <strong>Note:</strong> even if public this method is expected to be called only by core classes (for example,
     * {@code Control}).
     *
     * @param proxy the local proxy
     * @since 2.5.0
     */
    public void hookConnectRequestProxyListeners(Proxy proxy) {
        for (ExtensionHook hook : extensionHooks.values()) {
            hookConnectRequestProxyListeners(proxy, hook.getConnectRequestProxyListeners());
        }
    }

    private static void hookConnectRequestProxyListeners(Proxy proxy, List<ConnectRequestProxyListener> listeners) {
        for (ConnectRequestProxyListener listener : listeners) {
            proxy.addConnectRequestProxyListener(listener);
        }
    }

    private void removeConnectRequestProxyListener(ExtensionHook hook) {
        Proxy proxy = Control.getSingleton().getProxy();
        for (ConnectRequestProxyListener listener : hook.getConnectRequestProxyListeners()) {
            proxy.removeConnectRequestProxyListener(listener);
        }
    }

    public void hookPersistentConnectionListener(Proxy proxy) {
        for (ExtensionHook hook : extensionHooks.values()) {
            hookPersistentConnectionListeners(proxy, hook.getPersistentConnectionListener());
        }
    }

    private static void hookPersistentConnectionListeners(Proxy proxy,
            List<PersistentConnectionListener> listeners) {
        for (PersistentConnectionListener listener : listeners) {
            try {
                if (listener != null) {
                    proxy.addPersistentConnectionListener(listener);
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private void removePersistentConnectionListener(ExtensionHook hook) {
        Proxy proxy = Control.getSingleton().getProxy();
        List<PersistentConnectionListener> listenerList = hook.getPersistentConnectionListener();
        for (PersistentConnectionListener listener : listenerList) {
            try {
                if (listener != null) {
                    proxy.removePersistentConnectionListener(listener);
                }

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    // ZAP: Added support for site map listeners
    public void hookSiteMapListener(SiteMapPanel siteMapPanel) {
        for (ExtensionHook hook : extensionHooks.values()) {
            hookSiteMapListeners(siteMapPanel, hook.getSiteMapListenerList());
        }
    }

    private static void hookSiteMapListeners(SiteMapPanel siteMapPanel, List<SiteMapListener> listeners) {
        for (SiteMapListener listener : listeners) {
            try {
                if (listener != null) {
                    siteMapPanel.addSiteMapListener(listener);
                }
            } catch (Exception e) {
                // ZAP: Log the exception
                logger.error(e.getMessage(), e);
            }
        }
    }

    private void removeSiteMapListener(ExtensionHook hook) {
        if (view != null) {
            SiteMapPanel siteMapPanel = view.getSiteTreePanel();
            List<SiteMapListener> listenerList = hook.getSiteMapListenerList();
            for (SiteMapListener listener : listenerList) {
                try {
                    if (listener != null) {
                        siteMapPanel.removeSiteMapListener(listener);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    // ZAP: method called by the scanner to load all scanner hooks. 
    public void hookScannerHook(Scanner scan) {
        Iterator<ExtensionHook> iter = extensionHooks.values().iterator();
        while (iter.hasNext()) {
            ExtensionHook hook = iter.next();
            List<ScannerHook> scannerHookList = hook.getScannerHookList();

            for (ScannerHook scannerHook : scannerHookList) {
                try {
                    if (hook != null) {
                        scan.addScannerHook(scannerHook);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void optionsChangedAllPlugin(OptionsParam options) {
        for (ExtensionHook hook : extensionHooks.values()) {
            List<OptionsChangedListener> listenerList = hook.getOptionsChangedListenerList();
            for (OptionsChangedListener listener : listenerList) {
                try {
                    if (listener != null) {
                        listener.optionsChanged(options);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void runCommandLine() {
        Extension ext;
        for (int i = 0; i < getExtensionCount(); i++) {
            ext = getExtension(i);
            if (ext instanceof CommandLineListener) {
                CommandLineListener listener = (CommandLineListener) ext;
                listener.execute(extensionHooks.get(ext).getCommandLineArgument());
            }
        }
    }

    public void sessionChangedAllPlugin(Session session) {
        logger.debug("sessionChangedAllPlugin");
        for (ExtensionHook hook : extensionHooks.values()) {
            List<SessionChangedListener> listenerList = hook.getSessionListenerList();
            for (SessionChangedListener listener : listenerList) {
                try {
                    if (listener != null) {
                        listener.sessionChanged(session);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void databaseOpen(Database db) {
        Extension ext;
        for (int i = 0; i < getExtensionCount(); i++) {
            ext = getExtension(i);
            try {
                ext.databaseOpen(db);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    public void sessionAboutToChangeAllPlugin(Session session) {
        logger.debug("sessionAboutToChangeAllPlugin");
        for (ExtensionHook hook : extensionHooks.values()) {
            List<SessionChangedListener> listenerList = hook.getSessionListenerList();
            for (SessionChangedListener listener : listenerList) {
                try {
                    if (listener != null) {
                        listener.sessionAboutToChange(session);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void sessionScopeChangedAllPlugin(Session session) {
        logger.debug("sessionScopeChangedAllPlugin");
        for (ExtensionHook hook : extensionHooks.values()) {
            List<SessionChangedListener> listenerList = hook.getSessionListenerList();
            for (SessionChangedListener listener : listenerList) {
                try {
                    if (listener != null) {
                        listener.sessionScopeChanged(session);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void sessionModeChangedAllPlugin(Mode mode) {
        logger.debug("sessionModeChangedAllPlugin");
        for (ExtensionHook hook : extensionHooks.values()) {
            List<SessionChangedListener> listenerList = hook.getSessionListenerList();
            for (SessionChangedListener listener : listenerList) {
                try {
                    if (listener != null) {
                        listener.sessionModeChanged(mode);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Notifies that the properties (e.g. name, description) of the current session were changed.
     * <p>
     * Should be called only by "core" classes.
     * 
     * @param session the session changed.
     * @since 2.7.0
     */
    public void sessionPropertiesChangedAllPlugin(Session session) {
        logger.debug("sessionPropertiesChangedAllPlugin");
        for (ExtensionHook hook : extensionHooks.values()) {
            for (SessionChangedListener listener : hook.getSessionListenerList()) {
                try {
                    if (listener != null) {
                        listener.sessionPropertiesChanged(session);
                    }

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void addonFilesAdded() {
        for (ExtensionHook hook : extensionHooks.values()) {
            List<AddonFilesChangedListener> listenerList = hook.getAddonFilesChangedListener();
            for (AddonFilesChangedListener listener : listenerList) {
                try {
                    listener.filesAdded();

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void addonFilesRemoved() {
        for (ExtensionHook hook : extensionHooks.values()) {
            List<AddonFilesChangedListener> listenerList = hook.getAddonFilesChangedListener();
            for (AddonFilesChangedListener listener : listenerList) {
                try {
                    listener.filesRemoved();

                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Notifies {@code Extension}s' {@code AddOnInstallationStatusListener}s that the given add-on was installed.
     *
     * @param addOn the add-on that was installed, must not be {@code null}
     * @since 2.5.0
     */
    public void addOnInstalled(AddOn addOn) {
        for (ExtensionHook hook : extensionHooks.values()) {
            for (AddOnInstallationStatusListener listener : hook.getAddOnInstallationStatusListeners()) {
                try {
                    listener.addOnInstalled(addOn);
                } catch (Exception e) {
                    logger.error("An error occurred while notifying: " + listener.getClass().getCanonicalName(), e);
                }
            }
        }
    }

    /**
     * Notifies {@code Extension}s' {@code AddOnInstallationStatusListener}s that the given add-on was soft uninstalled.
     *
     * @param addOn the add-on that was soft uninstalled, must not be {@code null}
     * @param successfully if the soft uninstallation was successful, that is, no errors occurred while uninstalling it
     * @since 2.5.0
     */
    public void addOnSoftUninstalled(AddOn addOn, boolean successfully) {
        for (ExtensionHook hook : extensionHooks.values()) {
            for (AddOnInstallationStatusListener listener : hook.getAddOnInstallationStatusListeners()) {
                try {
                    listener.addOnSoftUninstalled(addOn, successfully);
                } catch (Exception e) {
                    logger.error("An error occurred while notifying: " + listener.getClass().getCanonicalName(), e);
                }
            }
        }
    }

    /**
     * Notifies {@code Extension}s' {@code AddOnInstallationStatusListener}s that the given add-on was uninstalled.
     *
     * @param addOn the add-on that was uninstalled, must not be {@code null}
     * @param successfully if the uninstallation was successful, that is, no errors occurred while uninstalling it
     * @since 2.5.0
     */
    public void addOnUninstalled(AddOn addOn, boolean successfully) {
        for (ExtensionHook hook : extensionHooks.values()) {
            for (AddOnInstallationStatusListener listener : hook.getAddOnInstallationStatusListeners()) {
                try {
                    listener.addOnUninstalled(addOn, successfully);
                } catch (Exception e) {
                    logger.error("An error occurred while notifying: " + listener.getClass().getCanonicalName(), e);
                }
            }
        }
    }

    public void startAllExtension(double progressFactor) {
        double factorPerc = progressFactor / getExtensionCount();

        for (int i = 0; i < getExtensionCount(); i++) {
            Extension extension = getExtension(i);
            try {
                extension.start();
                if (view != null) {
                    view.addSplashScreenLoadingCompletion(factorPerc);
                }

            } catch (Exception e) {
                logExtensionInitError(extension, e);
            }
        }
    }

    /**
     * Initialize and start all Extensions
     * This function loops for all getExtensionCount() exts
     * launching each specific initialization element (model, xml, view, hook, etc.)
     */
    public void startLifeCycle() {

        // Percentages are passed into the calls as doubles
        if (view != null) {
            view.setSplashScreenLoadingCompletion(0.0);
        }

        // Step 3: initialize all (slow)
        initAllExtension(5.0);
        // Step 4: initialize models (quick)
        initModelAllExtension(model, 0.0);
        // Step 5: initialize xmls (quick)
        initXMLAllExtension(model.getSession(), model.getOptionsParam(), 0.0);
        // Step 6: initialize viewes (slow)
        initViewAllExtension(view, 10.0);
        // Step 7: initialize hooks (slowest)
        hookAllExtension(75.0);
        // Step 8: start all extensions(quick)
        startAllExtension(10.0);
    }

    /**
     * Initialize a specific Extension
     * @param ext the Extension that need to be initialized
     * @throws DatabaseUnsupportedException 
     * @throws DatabaseException 
     */
    public void startLifeCycle(Extension ext) throws DatabaseException, DatabaseUnsupportedException {
        ext.init();
        ext.databaseOpen(model.getDb());
        ext.initModel(model);
        ext.initXML(model.getSession(), model.getOptionsParam());
        ext.initView(view);

        ExtensionHook extHook = new ExtensionHook(model, view);
        try {
            ext.hook(extHook);
            extensionHooks.put(ext, extHook);

            hookContextDataFactories(ext, extHook);
            hookApiImplementors(ext, extHook);

            if (view != null) {
                // no need to hook view if no GUI
                hookView(ext, view, extHook);
                hookMenu(view, extHook);
            }

            hookOptions(extHook);
            ext.optionsLoaded();
            ext.postInit();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

        ext.start();

        Proxy proxy = Control.getSingleton().getProxy();
        hookProxyListeners(proxy, extHook.getProxyListenerList());

        hookPersistentConnectionListeners(proxy, extHook.getPersistentConnectionListener());
        hookConnectRequestProxyListeners(proxy, extHook.getConnectRequestProxyListeners());

        if (view != null) {
            hookSiteMapListeners(view.getSiteTreePanel(), extHook.getSiteMapListenerList());
        }
    }

    public void stopAllExtension() {
        for (int i = 0; i < getExtensionCount(); i++) {
            try {
                getExtension(i).stop();

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }

    }

    // ZAP: Added the type argument.
    private void addParamPanel(List<AbstractParamPanel> panelList, AbstractParamDialog dialog) {
        String[] ROOT = {};
        for (AbstractParamPanel panel : panelList) {
            try {
                dialog.addParamPanel(ROOT, panel, true);

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }

    }

    private void removeParamPanel(List<AbstractParamPanel> panelList, AbstractParamDialog dialog) {
        for (AbstractParamPanel panel : panelList) {
            try {
                dialog.removeParamPanel(panel);

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
        dialog.revalidate();
    }

    private void hookAllExtension(double progressFactor) {
        final double factorPerc = progressFactor / getExtensionCount();

        for (int i = 0; i < getExtensionCount(); i++) {
            final Extension ext = getExtension(i);
            try {
                logger.info("Initializing " + ext.getDescription());
                final ExtensionHook extHook = new ExtensionHook(model, view);
                ext.hook(extHook);
                extensionHooks.put(ext, extHook);

                hookContextDataFactories(ext, extHook);
                hookApiImplementors(ext, extHook);
                hookHttpSenderListeners(ext, extHook);

                if (view != null) {
                    EventQueue.invokeAndWait(new Runnable() {

                        @Override
                        public void run() {
                            // no need to hook view if no GUI
                            hookView(ext, view, extHook);
                            hookMenu(view, extHook);
                            view.addSplashScreenLoadingCompletion(factorPerc);
                        }
                    });
                }

                hookOptions(extHook);
                ext.optionsLoaded();

            } catch (Throwable e) {
                // Catch Errors thrown by out of date extensions as well as Exceptions
                logExtensionInitError(ext, e);
            }
        }
        // Call postInit for all extensions after they have all been initialized
        for (int i = 0; i < getExtensionCount(); i++) {
            Extension extension = getExtension(i);
            try {
                extension.postInit();
            } catch (Throwable e) {
                // Catch Errors thrown by out of date extensions as well as Exceptions
                logExtensionInitError(extension, e);
            }
        }

        if (view != null) {
            view.getMainFrame().getMainMenuBar().validate();
            view.getMainFrame().validate();
        }
    }

    private static void logExtensionInitError(Extension extension, Throwable e) {
        StringBuilder strBuilder = new StringBuilder(150);
        strBuilder.append("Failed to initialise extension ");
        strBuilder.append(extension.getClass().getCanonicalName());
        AddOn addOn = extension.getAddOn();
        if (addOn != null) {
            strBuilder.append(" (from add-on ").append(addOn).append(')');
        }
        strBuilder.append(", cause: ");
        strBuilder.append(ExceptionUtils.getRootCauseMessage(e));
        logger.error(strBuilder.toString(), e);
    }

    private void hookContextDataFactories(Extension extension, ExtensionHook extHook) {
        for (ContextDataFactory contextDataFactory : extHook.getContextDataFactories()) {
            try {
                model.addContextDataFactory(contextDataFactory);
            } catch (Exception e) {
                logger.error(
                        "Error while adding a ContextDataFactory from " + extension.getClass().getCanonicalName(),
                        e);
            }
        }
    }

    private void hookApiImplementors(Extension extension, ExtensionHook extHook) {
        for (ApiImplementor apiImplementor : extHook.getApiImplementors()) {
            try {
                API.getInstance().registerApiImplementor(apiImplementor);
            } catch (Exception e) {
                logger.error("Error while adding an ApiImplementor from " + extension.getClass().getCanonicalName(),
                        e);
            }
        }
    }

    private void hookHttpSenderListeners(Extension extension, ExtensionHook extHook) {
        for (HttpSenderListener httpSenderListener : extHook.getHttpSenderListeners()) {
            try {
                HttpSender.addListener(httpSenderListener);
            } catch (Exception e) {
                logger.error(
                        "Error while adding an HttpSenderListener from " + extension.getClass().getCanonicalName(),
                        e);
            }
        }
    }

    /**
     * Hook command line listener with the command line processor
     *
     * @param cmdLine
     * @throws java.lang.Exception
     */
    public void hookCommandLineListener(CommandLine cmdLine) throws Exception {
        List<CommandLineArgument[]> allCommandLineList = new ArrayList<>();
        Map<String, CommandLineListener> extMap = new HashMap<>();
        for (Map.Entry<Extension, ExtensionHook> entry : extensionHooks.entrySet()) {
            ExtensionHook hook = entry.getValue();
            CommandLineArgument[] arg = hook.getCommandLineArgument();
            if (arg.length > 0) {
                allCommandLineList.add(arg);
            }

            Extension extension = entry.getKey();
            if (extension instanceof CommandLineListener) {
                CommandLineListener cli = (CommandLineListener) extension;
                List<String> exts = cli.getHandledExtensions();
                if (exts != null) {
                    for (String ext : exts) {
                        extMap.put(ext, cli);
                    }
                }
            }
        }

        cmdLine.parse(allCommandLineList, extMap);
    }

    private void hookMenu(View view, ExtensionHook hook) {
        if (view == null) {
            return;
        }

        ExtensionHookMenu hookMenu = hook.getHookMenu();
        if (hookMenu == null) {
            return;
        }

        MainMenuBar menuBar = view.getMainFrame().getMainMenuBar();

        // 2 menus at the back (Tools/Help)
        addMenuHelper(menuBar, hookMenu.getNewMenus(), 2);

        addMenuHelper(menuBar.getMenuFile(), hookMenu.getFile(), 2);
        addMenuHelper(menuBar.getMenuTools(), hookMenu.getTools(), 2);
        addMenuHelper(menuBar.getMenuEdit(), hookMenu.getEdit());
        addMenuHelper(menuBar.getMenuView(), hookMenu.getView());
        addMenuHelper(menuBar.getMenuAnalyse(), hookMenu.getAnalyse());
        addMenuHelper(menuBar.getMenuHelp(), hookMenu.getHelpMenus());
        addMenuHelper(menuBar.getMenuReport(), hookMenu.getReportMenus());
        addMenuHelper(menuBar.getMenuOnline(), hookMenu.getOnlineMenus());

        addMenuHelper(view.getPopupList(), hookMenu.getPopupMenus());
    }

    private void addMenuHelper(JMenu menu, List<JMenuItem> items) {
        addMenuHelper(menu, items, 0);
    }

    private void addMenuHelper(JMenuBar menuBar, List<JMenuItem> items, int existingCount) {
        for (JMenuItem item : items) {
            if (item != null) {
                menuBar.add(item, menuBar.getMenuCount() - existingCount);
            }
        }
        menuBar.revalidate();
    }

    private void addMenuHelper(JMenu menu, List<JMenuItem> items, int existingCount) {
        for (JMenuItem item : items) {
            if (item != null) {
                if (item == ExtensionHookMenu.MENU_SEPARATOR) {
                    menu.addSeparator();
                    continue;
                }

                menu.add(item, menu.getItemCount() - existingCount);
            }
        }

        menu.revalidate();
    }

    private void addMenuHelper(List<JMenuItem> menuList, List<JMenuItem> items) {
        for (JMenuItem item : items) {
            if (item != null) {
                menuList.add(item);
            }
        }
    }

    private void removeMenu(View view, ExtensionHook hook) {
        if (view == null) {
            return;
        }

        ExtensionHookMenu hookMenu = hook.getHookMenu();
        if (hookMenu == null) {
            return;
        }

        MainMenuBar menuBar = view.getMainFrame().getMainMenuBar();

        // clear up various menus
        removeMenuHelper(menuBar, hookMenu.getNewMenus());

        removeMenuHelper(menuBar.getMenuFile(), hookMenu.getFile());
        removeMenuHelper(menuBar.getMenuTools(), hookMenu.getTools());
        removeMenuHelper(menuBar.getMenuEdit(), hookMenu.getEdit());
        removeMenuHelper(menuBar.getMenuView(), hookMenu.getView());
        removeMenuHelper(menuBar.getMenuAnalyse(), hookMenu.getAnalyse());
        removeMenuHelper(menuBar.getMenuHelp(), hookMenu.getHelpMenus());
        removeMenuHelper(menuBar.getMenuReport(), hookMenu.getReportMenus());
        removeMenuHelper(menuBar.getMenuOnline(), hookMenu.getOnlineMenus());

        removeMenuHelper(view.getPopupList(), hookMenu.getPopupMenus());

        view.refreshTabViewMenus();
    }

    private void removeMenuHelper(JMenuBar menuBar, List<JMenuItem> items) {
        for (JMenuItem item : items) {
            if (item != null) {
                menuBar.remove(item);
            }
        }
        menuBar.revalidate();
    }

    private void removeMenuHelper(JMenu menu, List<JMenuItem> items) {
        for (JMenuItem item : items) {
            if (item != null) {
                menu.remove(item);
            }
        }
        menu.revalidate();
    }

    private void removeMenuHelper(List<JMenuItem> menuList, List<JMenuItem> items) {
        for (JMenuItem item : items) {
            if (item != null) {
                menuList.remove(item);
            }
        }
    }

    private void hookOptions(ExtensionHook hook) {
        List<AbstractParam> list = hook.getOptionsParamSetList();

        for (AbstractParam paramSet : list) {
            try {
                model.getOptionsParam().addParamSet(paramSet);

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private void unloadOptions(ExtensionHook hook) {
        List<AbstractParam> list = hook.getOptionsParamSetList();

        for (AbstractParam paramSet : list) {
            try {
                model.getOptionsParam().removeParamSet(paramSet);

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
    }

    private void hookView(Extension extension, View view, ExtensionHook hook) {
        if (view == null) {
            return;
        }

        ExtensionHookView pv = hook.getHookView();
        if (pv == null) {
            return;
        }

        for (ContextPanelFactory contextPanelFactory : pv.getContextPanelFactories()) {
            try {
                view.addContextPanelFactory(contextPanelFactory);
            } catch (Exception e) {
                logger.error(
                        "Error while adding a ContextPanelFactory from " + extension.getClass().getCanonicalName(),
                        e);
            }
        }

        MainToolbarPanel mainToolBarPanel = view.getMainFrame().getMainToolbarPanel();
        for (Component component : pv.getMainToolBarComponents()) {
            try {
                mainToolBarPanel.addToolBarComponent(component);
            } catch (Exception e) {
                logger.error("Error while adding a component to the main tool bar panel, from "
                        + extension.getClass().getCanonicalName(), e);
            }
        }

        view.getWorkbench().addPanels(pv.getSelectPanel(), WorkbenchPanel.PanelType.SELECT);
        view.getWorkbench().addPanels(pv.getWorkPanel(), WorkbenchPanel.PanelType.WORK);
        view.getWorkbench().addPanels(pv.getStatusPanel(), WorkbenchPanel.PanelType.STATUS);

        addParamPanel(pv.getSessionPanel(), view.getSessionDialog());
        addParamPanel(pv.getOptionsPanel(), view.getOptionsDialog(""));
    }

    private void removeView(Extension extension, View view, ExtensionHook hook) {
        if (view == null) {
            return;
        }

        ExtensionHookView pv = hook.getHookView();
        if (pv == null) {
            return;
        }

        for (ContextPanelFactory contextPanelFactory : pv.getContextPanelFactories()) {
            try {
                view.removeContextPanelFactory(contextPanelFactory);
            } catch (Exception e) {
                logger.error("Error while removing a ContextPanelFactory from "
                        + extension.getClass().getCanonicalName(), e);
            }
        }

        MainToolbarPanel mainToolBarPanel = view.getMainFrame().getMainToolbarPanel();
        for (Component component : pv.getMainToolBarComponents()) {
            try {
                mainToolBarPanel.removeToolBarComponent(component);
            } catch (Exception e) {
                logger.error("Error while removing a component from the main tool bar panel, from "
                        + extension.getClass().getCanonicalName(), e);
            }
        }

        view.getWorkbench().removePanels(pv.getSelectPanel(), WorkbenchPanel.PanelType.SELECT);
        view.getWorkbench().removePanels(pv.getWorkPanel(), WorkbenchPanel.PanelType.WORK);
        view.getWorkbench().removePanels(pv.getStatusPanel(), WorkbenchPanel.PanelType.STATUS);

        removeParamPanel(pv.getSessionPanel(), view.getSessionDialog());
        removeParamPanel(pv.getOptionsPanel(), view.getOptionsDialog(""));
    }

    public void removeStatusPanel(AbstractPanel panel) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getWorkbench().removePanel(panel, WorkbenchPanel.PanelType.STATUS);
    }

    public void removeOptionsPanel(AbstractParamPanel panel) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getOptionsDialog("").removeParamPanel(panel);
    }

    public void removeOptionsParamSet(AbstractParam params) {
        model.getOptionsParam().removeParamSet(params);
    }

    public void removeWorkPanel(AbstractPanel panel) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getWorkbench().removePanel(panel, WorkbenchPanel.PanelType.WORK);
    }

    public void removePopupMenuItem(ExtensionPopupMenuItem popupMenuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getPopupList().remove(popupMenuItem);
    }

    public void removeFileMenuItem(JMenuItem menuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getMainFrame().getMainMenuBar().getMenuFile().remove(menuItem);
    }

    public void removeEditMenuItem(JMenuItem menuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getMainFrame().getMainMenuBar().getMenuEdit().remove(menuItem);
    }

    public void removeViewMenuItem(JMenuItem menuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getMainFrame().getMainMenuBar().getMenuView().remove(menuItem);
    }

    public void removeToolsMenuItem(JMenuItem menuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getMainFrame().getMainMenuBar().getMenuTools().remove(menuItem);
    }

    public void removeHelpMenuItem(JMenuItem menuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getMainFrame().getMainMenuBar().getMenuHelp().remove(menuItem);
    }

    public void removeReportMenuItem(JMenuItem menuItem) {
        if (!View.isInitialised()) {
            return;
        }

        View.getSingleton().getMainFrame().getMainMenuBar().getMenuReport().remove(menuItem);
    }

    /**
     * Init all extensions
     */
    private void initAllExtension(double progressFactor) {
        double factorPerc = progressFactor / getExtensionCount();

        for (int i = 0; i < getExtensionCount(); i++) {
            Extension extension = getExtension(i);
            try {
                extension.init();
                extension.databaseOpen(Model.getSingleton().getDb());
                if (view != null) {
                    view.addSplashScreenLoadingCompletion(factorPerc);
                }

            } catch (Throwable e) {
                logExtensionInitError(extension, e);
            }
        }
    }

    /**
     * Init all extensions with the same Model
     * @param model the model to apply to all extensions
     */
    private void initModelAllExtension(Model model, double progressFactor) {
        double factorPerc = progressFactor / getExtensionCount();

        for (int i = 0; i < getExtensionCount(); i++) {
            Extension extension = getExtension(i);
            try {
                extension.initModel(model);
                if (view != null) {
                    view.addSplashScreenLoadingCompletion(factorPerc);
                }

            } catch (Exception e) {
                logExtensionInitError(extension, e);
            }
        }
    }

    /**
     * Init all extensions with the same View
     * @param view the View that need to be applied
     */
    private void initViewAllExtension(final View view, double progressFactor) {
        if (view == null) {
            return;
        }

        final double factorPerc = progressFactor / getExtensionCount();

        for (int i = 0; i < getExtensionCount(); i++) {
            final Extension extension = getExtension(i);
            try {
                EventQueue.invokeAndWait(new Runnable() {

                    @Override
                    public void run() {
                        extension.initView(view);
                        view.addSplashScreenLoadingCompletion(factorPerc);
                    }
                });

            } catch (Exception e) {
                logExtensionInitError(extension, e);
            }
        }
    }

    private void initXMLAllExtension(Session session, OptionsParam options, double progressFactor) {
        double factorPerc = progressFactor / getExtensionCount();

        for (int i = 0; i < getExtensionCount(); i++) {
            Extension extension = getExtension(i);
            try {
                extension.initXML(session, options);
                if (view != null) {
                    view.addSplashScreenLoadingCompletion(factorPerc);
                }

            } catch (Exception e) {
                logExtensionInitError(extension, e);
            }
        }
    }

    /**
     * Removes an extension from internal list. As a result listeners added via
     * the {@link ExtensionHook} object are unregistered.
     *
     * @param extension
     * @param hook
     */
    public void removeExtension(Extension extension, ExtensionHook hook) {
        extensionList.remove(extension);
        extensionsMap.remove(extension.getClass());

        if (hook == null) {
            logger.info("ExtensionHook is null for \"" + extension.getClass().getCanonicalName()
                    + "\" the hooked objects will not be automatically removed.");
            return;
        }

        // by removing the ExtensionHook object,
        // the following listeners are no longer informed:
        //       * SessionListeners
        //       * OptionsChangedListeners
        extensionHooks.values().remove(hook);

        unloadOptions(hook);

        removePersistentConnectionListener(hook);

        removeProxyListener(hook);

        removeOverrideMessageProxyListener(hook);

        removeConnectRequestProxyListener(hook);

        removeSiteMapListener(hook);

        for (ContextDataFactory contextDataFactory : hook.getContextDataFactories()) {
            try {
                model.removeContextDataFactory(contextDataFactory);
            } catch (Exception e) {
                logger.error(
                        "Error while removing a ContextDataFactory from " + extension.getClass().getCanonicalName(),
                        e);
            }
        }

        for (ApiImplementor apiImplementor : hook.getApiImplementors()) {
            try {
                API.getInstance().removeApiImplementor(apiImplementor);
            } catch (Exception e) {
                logger.error(
                        "Error while removing an ApiImplementor from " + extension.getClass().getCanonicalName(),
                        e);
            }
        }

        for (HttpSenderListener httpSenderListener : hook.getHttpSenderListeners()) {
            try {
                HttpSender.removeListener(httpSenderListener);
            } catch (Exception e) {
                logger.error("Error while removing an HttpSenderListener from "
                        + extension.getClass().getCanonicalName(), e);
            }
        }

        removeViewInEDT(extension, hook);
    }

    private void removeViewInEDT(final Extension extension, final ExtensionHook hook) {
        if (view == null) {
            return;
        }

        if (EventQueue.isDispatchThread()) {
            removeView(extension, view, hook);
            removeMenu(view, hook);
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    removeViewInEDT(extension, hook);
                }
            });
        }
    }

    /**
     * Gets the names of all unsaved resources of all the extensions.
     *
     * @return a {@code List} containing all the unsaved resources of all add-ons, never {@code null}
     * @see Extension#getActiveActions()
     */
    public List<String> getUnsavedResources() {
        List<String> list = new ArrayList<>();
        List<String> l;

        for (int i = 0; i < getExtensionCount(); i++) {
            l = getExtension(i).getUnsavedResources();
            if (l != null) {
                list.addAll(l);
            }
        }

        return list;
    }

    /**
     * Gets the names of all active actions of all the extensions.
     *
     * @return a {@code List} containing all the active actions of all add-ons, never {@code null}
     * @since 2.4.0
     * @see Extension#getActiveActions()
     */
    public List<String> getActiveActions() {
        List<String> list = new ArrayList<>();
        List<String> l;

        for (int i = 0; i < getExtensionCount(); i++) {
            l = getExtension(i).getActiveActions();
            if (l != null) {
                list.addAll(l);
            }
        }

        return list;
    }

}