org.pentaho.marketplace.domain.services.BasePluginService.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.marketplace.domain.services.BasePluginService.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 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 Lesser General Public License for more details.
 *
 * Copyright (c) 2015 Pentaho Corporation. All rights reserved.
 */

package org.pentaho.marketplace.domain.services;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.kar.KarService;

import org.apache.commons.io.FileUtils;

import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;

import org.pentaho.marketplace.domain.model.entities.interfaces.IDomainStatusMessage;
import org.pentaho.marketplace.domain.model.entities.interfaces.IPlugin;
import org.pentaho.marketplace.domain.model.entities.interfaces.IPluginVersion;
import org.pentaho.marketplace.domain.model.entities.interfaces.IVersionData;
import org.pentaho.marketplace.domain.model.factories.interfaces.IDomainStatusMessageFactory;
import org.pentaho.marketplace.domain.model.factories.interfaces.IPluginVersionFactory;
import org.pentaho.marketplace.domain.model.factories.interfaces.IVersionDataFactory;
import org.pentaho.marketplace.domain.services.interfaces.IPluginProvider;
import org.pentaho.marketplace.domain.services.interfaces.IPluginService;
import org.pentaho.marketplace.domain.services.interfaces.IRemotePluginProvider;
import org.pentaho.telemetry.ITelemetryService;
import org.pentaho.telemetry.TelemetryEvent;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public abstract class BasePluginService implements IPluginService {

    //region Inner Definitions
    protected static class MarketplaceSecurityException extends Exception {

        private static final long serialVersionUID = -1852471739131561628L;
    }
    //endregion

    //region Constants

    // Error messages codes should begin with ERROR
    protected static final String UNAUTHORIZED_ACCESS_MESSAGE = "Unauthorized Access.";
    protected static final String UNAUTHORIZED_ACCESS_ERROR_CODE = "ERROR_0002_UNAUTHORIZED_ACCESS";
    protected static final String NO_PLUGIN_ERROR_CODE = "ERROR_0001_NO_PLUGIN";
    protected static final String FAIL_ERROR_CODE = "ERROR_0003_FAIL";
    protected static final String PLUGIN_INSTALLED_CODE = "PLUGIN_INSTALLED";
    protected static final String PLUGIN_UNINSTALLED_CODE = "PLUGIN_UNINSTALLED";

    protected static final String KARAF_FEATURES_CONFIG_PID = "org.apache.karaf.features";
    protected static final String KARAF_FEATURES_BOOT_PROPERTY_ID = "featuresBoot";
    protected static final String PENTAHO_FEATURES_CONFIG_PID = "org.pentaho.features";
    protected static final String PENTAHO_RUNTIME_FEATURES_PROPERTY_ID = "runtimeFeatures";

    //endregion

    //region Properties

    //region logger
    protected Log getLogger() {
        return this.logger;
    }

    protected Log logger = LogFactory.getLog(this.getClass());
    //endregion

    //region metadataPluginsProvider
    public IPluginProvider getMetadataPluginsProvider() {
        return this.metadataPluginsProvider;
    }

    protected BasePluginService setMetadataPluginsProvider(IPluginProvider provider) {
        this.metadataPluginsProvider = provider;
        return this;
    }

    private IPluginProvider metadataPluginsProvider;
    //endregion

    //region versionDataFactory
    public IVersionDataFactory getVersionDataFactory() {
        return this.versionDataFactory;
    }

    protected void setVersionDataFactory(IVersionDataFactory versionDataFactory) {
        this.versionDataFactory = versionDataFactory;
    }

    private IVersionDataFactory versionDataFactory;
    //endregion

    //region pluginVersionFactory
    public IPluginVersionFactory getPluginVersionFactory() {
        return pluginVersionFactory;
    }

    protected void setPluginVersionFactory(IPluginVersionFactory pluginVersionFactory) {
        this.pluginVersionFactory = pluginVersionFactory;
    }

    private IPluginVersionFactory pluginVersionFactory;
    //endregion

    //region karService
    public KarService getKarService() {
        return this.karService;
    }

    protected void setKarService(KarService karService) {
        this.karService = karService;
    }

    private KarService karService;
    //endregion karService

    //region featureService
    public FeaturesService getFeaturesService() {
        return this.featuresService;
    }

    protected void setFeaturesService(FeaturesService featuresService) {
        this.featuresService = featuresService;
    }

    private FeaturesService featuresService;
    //endregion

    //region telemetryService
    public ITelemetryService getTelemetryService() {
        return this.telemetryService;
    }

    protected void setTelemetryService(ITelemetryService telemetryService) {
        this.telemetryService = telemetryService;
    }

    private ITelemetryService telemetryService;
    //endregion

    //region getDomainStatusMessageFactory
    public IDomainStatusMessageFactory getDomainStatusMessageFactory() {
        return this.domainStatusMessageFactory;
    }

    protected BasePluginService setDomainStatusMessageFactory(
            IDomainStatusMessageFactory domainStatusMessageFactory) {
        this.domainStatusMessageFactory = domainStatusMessageFactory;
        return this;
    }

    private IDomainStatusMessageFactory domainStatusMessageFactory;
    //endregion

    //region serverVersion
    protected String getServerVersion() {
        return this.serverVersion;
    }

    protected BasePluginService setServerVersion(String serverVersion) {
        this.serverVersion = serverVersion;
        return this;
    }

    private String serverVersion;
    //endregion

    protected ConfigurationAdmin getConfigurationAdmin() {
        return this.configurationAdmin;
    }

    protected BasePluginService setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
        this.configurationAdmin = configurationAdmin;
        return this;
    }

    private ConfigurationAdmin configurationAdmin;
    //endregion

    //region Constructors
    protected BasePluginService(IRemotePluginProvider metadataPluginsProvider,
            IVersionDataFactory versionDataFactory, IPluginVersionFactory pluginVersionFactory,
            KarService karService, FeaturesService featuresService, ConfigurationAdmin configurationAdmin,
            ITelemetryService telemetryService, IDomainStatusMessageFactory domainStatusMessageFactory) {
        //initialize dependencies
        this.setMetadataPluginsProvider(metadataPluginsProvider);
        this.setVersionDataFactory(versionDataFactory);
        this.setPluginVersionFactory(pluginVersionFactory);
        this.setKarService(karService);
        this.setFeaturesService(featuresService);
        this.setConfigurationAdmin(configurationAdmin);
        this.setTelemetryService(telemetryService);
        this.setDomainStatusMessageFactory(domainStatusMessageFactory);
    }
    //endregion

    //region Methods

    private boolean withinParentVersion(IPluginVersion pv) {
        // need to compare plugin version min and max parent with system version.
        // replace the version of the xml url path with the current release version:
        String v = this.getServerVersion();
        IVersionData pvMax = this.getVersionDataFactory().create(pv.getMaxParentVersion());
        IVersionData pvMin = this.getVersionDataFactory().create(pv.getMinParentVersion());
        IVersionData version = this.getVersionDataFactory().create(v);

        return version.within(pvMin, pvMax);
    }

    private Collection<IPluginVersion> getCompatibleVersionsWithParent(Iterable<IPluginVersion> versions) {
        Collection<IPluginVersion> compatibleVersions = new ArrayList<>();
        for (IPluginVersion version : versions) {
            if (withinParentVersion(version)) {
                compatibleVersions.add(version);
            }
        }

        return compatibleVersions;
    }

    /**
     * Filters out plugins without a compatible version to server. Removes non compatible versions for the plugins which
     * pass the filter.
     */
    private Map<String, IPlugin> removeNonCompatibleVersions(Iterable<IPlugin> plugins) {
        Map<String, IPlugin> pluginsWithCompatibleVersions = new HashMap<>();

        for (IPlugin plugin : plugins) {
            // filter out plugin versions that are not compatible with parent version
            Collection<IPluginVersion> compatibleVersions = this
                    .getCompatibleVersionsWithParent(plugin.getVersions());
            // only include plugins that have versions within this release
            if (compatibleVersions.size() > 0) {
                // change available version to only compatible ones
                plugin.setVersions(compatibleVersions);
                pluginsWithCompatibleVersions.put(plugin.getId(), plugin);
            }
        }

        return pluginsWithCompatibleVersions;
    }

    private boolean isPluginIdValid(String pluginId) {
        return pluginId != null && pluginId.length() > 0
        // this checks to make sure the plugin metadata isn't attempting to overwrite a folder on the system.
        // TODO: Test a .. encoded in UTF8, etc to see if there is a way to thwart this check
                && pluginId.indexOf(".") < 0;
    }

    public IPlugin getPlugin(String id) {
        return this.getPlugins().get(id);
    }

    // TODO: only allows one version per branch
    public IPluginVersion getPluginVersion(IPlugin plugin, String versionBranch) {
        if (versionBranch != null && versionBranch.length() > 0) {
            return plugin.getVersionByBranch(versionBranch);
        }
        return null;
    }

    /**
     * Filters plugins by plugin id
     */
    private Map<String, IPlugin> filterPlugins(Map<String, IPlugin> plugins, Collection<String> pluginIds) {
        if (pluginIds.size() < 1) {
            return Collections.emptyMap();
        }

        Map<String, IPlugin> filteredPlugins = new HashMap<>();

        for (String pluginId : pluginIds) {
            IPlugin plugin = plugins.get(pluginId);
            if (plugin != null) {
                filteredPlugins.put(pluginId, plugin);
            }
        }

        return filteredPlugins;
    }

    /**
     * Sets plugins as installed as well as the installed version
     *
     * @param plugins the plugins to be marked as installed
     */
    private void setPluginsAsInstalled(Collection<IPlugin> plugins) {
        for (IPlugin plugin : plugins) {
            plugin.setInstalled(true);
            IPluginVersion installedVersion = getInstalledPluginVersion(plugin);
            if (installedVersion != null) {
                plugin.setInstalledBranch(installedVersion.getBranch());
                plugin.setInstalledVersion(installedVersion.getVersion());
                plugin.setInstalledBuildId(installedVersion.getBuildId());
            }
        }
    }

    private void publishTelemetryEvent(TelemetryEvent.Type eventType, IPlugin plugin, IPluginVersion version) {
        try {
            ITelemetryService telemetryService = this.getTelemetryService();
            TelemetryEvent event = telemetryService.createEvent(eventType);
            event.getExtraInfo().put("installedPlugin", plugin.getId());
            event.getExtraInfo().put("installedBranch", version.getBranch());
            event.getExtraInfo().put("installedVersion", version.getVersion());
            telemetryService.publishEvent(event);
        } catch (NoClassDefFoundError e) {
            this.getLogger()
                    .debug("Failed to find class definitions. Most likely reason is reinstalling marketplace.", e);
        }
    }

    private IDomainStatusMessage upgradePluginAux(String pluginId, String versionBranch) {

        if (!hasMarketplacePermission()) {
            return this.getDomainStatusMessageFactory().create(UNAUTHORIZED_ACCESS_ERROR_CODE,
                    UNAUTHORIZED_ACCESS_MESSAGE);
        }

        if (!isPluginIdValid(pluginId)) {
            return this.getDomainStatusMessageFactory().create(NO_PLUGIN_ERROR_CODE, "Invalid plugin id");
        }

        IPlugin plugin = this.getPlugin(pluginId);
        if (plugin == null) {
            return this.getDomainStatusMessageFactory().create(NO_PLUGIN_ERROR_CODE, "Plugin not found");
        }

        IPluginVersion pluginVersionToInstall = plugin.getVersionByBranch(versionBranch);
        if (pluginVersionToInstall == null) {
            return this.getDomainStatusMessageFactory().create(NO_PLUGIN_ERROR_CODE,
                    "Plugin version for branch " + versionBranch + " not found");
        }

        // Perhaps we are reinstalling the marketplace.
        // Create telemetry event and messages before closing class loader just in case.
        ITelemetryService telemetryService = this.getTelemetryService();
        TelemetryEvent event = telemetryService.createEvent(TelemetryEvent.Type.UPGRADE);
        event.getExtraInfo().put("installedPlugin", plugin.getId());
        event.getExtraInfo().put("installedVersion", pluginVersionToInstall.getVersion());
        event.getExtraInfo().put("installedBranch", versionBranch);

        IDomainStatusMessage successMessage = this.domainStatusMessageFactory.create(PLUGIN_INSTALLED_CODE,
                plugin.getName() + " was successfully Upgraded.  Please restart. \n"
                        + plugin.getInstallationNotes());

        IDomainStatusMessage upgradeInstallFailureMessage = this.domainStatusMessageFactory.create(FAIL_ERROR_CODE,
                "Failed to install on plugin upgrade, see log for details.");

        IDomainStatusMessage upgradeUninstallFailureMessage = this.domainStatusMessageFactory
                .create(FAIL_ERROR_CODE, "Failed to uninstall on plugin upgrade, see log for details.");

        // it's an upgrade, uninstall old version first
        if (!this.executeUninstall(plugin)) {
            return upgradeUninstallFailureMessage;
        }

        // install new version
        if (!this.executeInstall(plugin, pluginVersionToInstall)) {
            return upgradeInstallFailureMessage;
        }

        try {
            telemetryService.publishEvent(event);
        } catch (NoClassDefFoundError e) {
            this.getLogger()
                    .debug("Failed to find class definitions. Most likely reason is reinstalling marketplace.", e);
        }

        return successMessage;
    }

    private IDomainStatusMessage installPluginAux(String pluginId, String versionBranch)
            throws MarketplaceSecurityException {

        if (!hasMarketplacePermission()) {
            throw new MarketplaceSecurityException();
        }

        if (!isPluginIdValid(pluginId)) {
            return this.domainStatusMessageFactory.create(NO_PLUGIN_ERROR_CODE, "Invalid Plugin Id.");
        }

        IPlugin toInstall = this.getPlugin(pluginId);
        if (toInstall == null) {
            return this.domainStatusMessageFactory.create(NO_PLUGIN_ERROR_CODE, "Plugin Not Found");
        }

        IPluginVersion versionToInstall = null;
        if (versionBranch != null && versionBranch.length() > 0) {
            versionToInstall = toInstall.getVersionByBranch(versionBranch);
            if (versionToInstall == null) {
                return this.domainStatusMessageFactory.create(NO_PLUGIN_ERROR_CODE, "Plugin version not found");
            }
        } else {
            return this.domainStatusMessageFactory.create(FAIL_ERROR_CODE,
                    "Version " + versionBranch + " not found for plugin " + pluginId + ", see log for details.");
        }

        // Perhaps we are reinstalling the marketplace.
        // Create telemetry event and messages before closing class loader just in case.
        ITelemetryService telemetryService = this.getTelemetryService();
        TelemetryEvent event = telemetryService.createEvent(TelemetryEvent.Type.INSTALLATION);
        event.getExtraInfo().put("installedPlugin", toInstall.getId());
        event.getExtraInfo().put("installedVersion", versionToInstall.getVersion());
        event.getExtraInfo().put("installedBranch", versionBranch);

        IDomainStatusMessage successMessage = this.domainStatusMessageFactory.create(PLUGIN_INSTALLED_CODE,
                toInstall.getName() + " was successfully installed.  Please restart your BI Server. \n"
                        + toInstall.getInstallationNotes());

        IDomainStatusMessage failureMessage = this.domainStatusMessageFactory.create(FAIL_ERROR_CODE,
                "Failed to execute install, see log for details.");

        if (!this.executeInstall(toInstall, versionToInstall)) {
            return failureMessage;
        }

        try {
            telemetryService.publishEvent(event);
        } catch (NoClassDefFoundError e) {
            this.getLogger()
                    .debug("Failed to find class definitions. Most likely reason is reinstalling marketplace.", e);
        }

        return successMessage;
    }

    private IDomainStatusMessage uninstallPluginAux(String pluginId) throws MarketplaceSecurityException {

        if (!hasMarketplacePermission()) {
            throw new MarketplaceSecurityException();
        }

        IPlugin toUninstall = this.getPlugin(pluginId);
        if (toUninstall == null) {
            return this.domainStatusMessageFactory.create(NO_PLUGIN_ERROR_CODE, "Plugin Not Found");
        }

        // Perhaps we are uninstalling the marketplace.
        // Create telemetry event and messages before closing class loader just in case.
        ITelemetryService telemetryService = this.getTelemetryService();
        TelemetryEvent event = telemetryService.createEvent(TelemetryEvent.Type.INSTALLATION);
        event.getExtraInfo().put("uninstalledPlugin", toUninstall.getId());
        event.getExtraInfo().put("uninstalledPluginVersion", toUninstall.getInstalledVersion());
        event.getExtraInfo().put("uninstalledPluginBranch", toUninstall.getInstalledBranch());

        IDomainStatusMessage successMessage = this.domainStatusMessageFactory.create(PLUGIN_UNINSTALLED_CODE,
                toUninstall.getName() + " was successfully uninstalled.  Please restart your BI Server.");

        IDomainStatusMessage failureMessage = this.domainStatusMessageFactory.create(FAIL_ERROR_CODE,
                "Failed to execute uninstall, see log for details.");

        if (!this.executeUninstall(toUninstall)) {
            return failureMessage;
        }

        try {
            telemetryService.publishEvent(event);
        } catch (NoClassDefFoundError e) {
            this.getLogger()
                    .debug("Failed to find class definitions. Most likely reason is uninstalling marketplace.", e);
        }

        return successMessage;
    }
    //endregion

    //region IPluginService implementation
    @Override
    public Map<String, IPlugin> getPlugins() {
        Map<String, IPlugin> marketplacePlugins = this.getMetadataPluginsProvider().getPlugins();

        Map<String, IPlugin> compatiblePlugins = this.removeNonCompatibleVersions(marketplacePlugins.values());

        Collection<String> installedPluginIds = getInstalledPluginIds();
        Map<String, IPlugin> installedPlugins = this.filterPlugins(compatiblePlugins, installedPluginIds);
        this.setPluginsAsInstalled(installedPlugins.values());

        return compatiblePlugins;
    }

    @Override
    public IDomainStatusMessage installPlugin(String pluginId, String versionBranch) {
        try {
            IPlugin plugin = this.getPlugin(pluginId);

            if (plugin != null && plugin.isInstalled()) {
                return this.upgradePluginAux(pluginId, versionBranch);
            } else {
                return this.installPluginAux(pluginId, versionBranch);
            }

        } catch (MarketplaceSecurityException e) {
            this.getLogger().debug(e.getMessage(), e);
            return this.domainStatusMessageFactory.create(UNAUTHORIZED_ACCESS_ERROR_CODE,
                    UNAUTHORIZED_ACCESS_MESSAGE);
        }
    }

    @Override
    public IDomainStatusMessage uninstallPlugin(String pluginId) {
        try {
            return uninstallPluginAux(pluginId);
        } catch (MarketplaceSecurityException e) {
            this.getLogger().debug(e.getMessage(), e);
            return this.domainStatusMessageFactory.create(UNAUTHORIZED_ACCESS_ERROR_CODE,
                    UNAUTHORIZED_ACCESS_MESSAGE);
        }
    }
    //endregion

    protected boolean executeOsgiInstallViaKarService(IPlugin plugin, IPluginVersion versionToInstall) {
        if (versionToInstall.isOsgi()) {
            this.getLogger().debug("## Install Osgi Plugin ##");
            try {
                URI uri = new URL(versionToInstall.getDownloadUrl()).toURI();
                this.getKarService().install(uri);
                // TODO: check if it was successful or not
                return true;
            } catch (Exception e) {
                this.getLogger().warn("Failed to install OSGi plugin.", e);
                return false;
            }
        }
        return false;
    }

    private String getKarafDeployFolder() {
        //TODO: hardcoded deploy folder. Check for better alternative
        return System.getProperty("karaf.base") + File.separator + "deploy";
    }

    protected boolean executeOsgiInstall(IPlugin plugin, IPluginVersion versionToInstall) {
        if (versionToInstall.isOsgi()) {
            this.getLogger().debug("Installing Osgi Plugin " + plugin.getId());
            try {
                String deployFolderName = this.getKarafDeployFolder();
                String downloadUrl = versionToInstall.getDownloadUrl();
                //String karName = FilenameUtils.getName( downloadUrl );
                File dlKarFile = new File(deployFolderName + File.separator + plugin.getId() + ".kar");
                FileUtils.copyURLToFile(new URL(downloadUrl), dlKarFile);

                // TODO: check if it was successful or not
                return true;
            } catch (Exception e) {
                this.getLogger().warn("Failed to install OSGi plugin " + plugin.getId(), e);
                return false;
            }
        }
        return false;
    }

    protected boolean executeOsgiUninstallViaKarService(IPlugin plugin) {
        this.getLogger().debug("Uninstalling Osgi Plugin " + plugin.getId());
        try {
            this.getKarService().uninstall(plugin.getId());
            // TODO: check if it was successful or not
            return true;
        } catch (Exception e) {
            this.getLogger().warn("Failed to uninstall OSGi plugin " + plugin.getId(), e);
            return false;
        }
    }

    protected boolean executeOsgiUninstall(IPlugin plugin) {
        String pluginId = plugin.getId();
        this.removeFeatureFromKarafBoot(pluginId);
        try {
            this.getFeaturesService().uninstallFeature(pluginId);
        } catch (Exception e) {
            this.getLogger()
                    .debug("No installed feature found with name " + pluginId + " when uninstalling OSGI plugin.");
        }

        // remove KAR file from deploy folder if it exists
        String deployFolder = this.getKarafDeployFolder();
        File karFile = new File(deployFolder + File.separator + pluginId + ".kar");
        if (karFile.exists() && karFile.isFile()) {
            return FileUtils.deleteQuietly(karFile);
        }
        return true;
    }

    private void removeFeatureFromKarafBoot(String featureName) {
        this.removeFeatureFromKarafBoot(featureName, KARAF_FEATURES_CONFIG_PID, KARAF_FEATURES_BOOT_PROPERTY_ID);
        this.removeFeatureFromKarafBoot(featureName, PENTAHO_FEATURES_CONFIG_PID,
                PENTAHO_RUNTIME_FEATURES_PROPERTY_ID);
    }

    private void removeFeatureFromKarafBoot(String featureName, String configurationPid, String propertyId) {
        ConfigurationAdmin configurationAdmin = this.getConfigurationAdmin();
        Log logger = this.getLogger();

        try {
            Configuration configuration = configurationAdmin.getConfiguration(configurationPid);
            Dictionary<String, Object> properties = configuration.getProperties();
            if (properties == null) {
                logger.debug("Configuration " + configurationPid + " has no properties.");
                return;
            }
            String propertyValue = (String) properties.get(propertyId);
            if (propertyValue == null) {
                logger.debug("Property " + propertyId + " not set in configuration " + configurationPid + ".");
                return;
            }

            String newPropertyValue = propertyValue.replaceFirst("," + featureName, "");
            if (!propertyValue.equals(newPropertyValue)) {
                properties.put(propertyId, newPropertyValue);
                configuration.update(properties);
            }
        } catch (IOException e) {
            logger.debug("Unable to access configuration " + configurationPid + ".");
        }
    }

    private IPluginVersion getInstalledOsgiPluginVersion(IPlugin plugin) {
        this.getLogger().debug("Infer Version from installed Osgi Plugin");
        // search installed features for plugin id
        IPluginVersion installedOsgiPluginVersion = this.getInstalledOsgiPluginVersionFromFeatures(plugin);
        if (installedOsgiPluginVersion == null) {
            // If no feature with the plugin id is found, check installed KARs
            installedOsgiPluginVersion = this.getInstalledOsgiPluginVersionFromKars(plugin);
        }
        return installedOsgiPluginVersion;
    }

    private IPluginVersion getInstalledOsgiPluginVersionFromKars(IPlugin plugin) {
        try {
            if (this.getKarService().list().contains(plugin.getId())) {
                IPluginVersion installedPluginVersion = this.getPluginVersionFactory().create();
                installedPluginVersion.setIsOsgi(true);
                // TODO: add branch / version / buildId information that currently is not available
                return installedPluginVersion;
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }

    private IPluginVersion getInstalledOsgiPluginVersionFromFeatures(IPlugin plugin) {
        this.getLogger().debug("## Infer Version from Karaf features ##");
        try {
            String pluginId = plugin.getId();
            Feature feature = this.getFeaturesService().getFeature(pluginId);
            if (feature != null) {
                IPluginVersion installedPluginVersion = this.getPluginVersionFactory().create();
                installedPluginVersion.setIsOsgi(true);
                // installedPluginVersion.setName( feature.getName() );
                installedPluginVersion.setVersion(feature.getVersion());
                installedPluginVersion.setBranch(null);
                installedPluginVersion.setBuildId(null);

                return installedPluginVersion;
            }
        } catch (Exception e) {
            this.getLogger().warn("Failed to infer version of installed OSGi plugin from features.", e);
            return null;
        }
        return null;
    }

    private IPluginVersion getInstalledPluginVersion(IPlugin plugin) {
        IPluginVersion osgiPluginVersion = this.getInstalledOsgiPluginVersion(plugin);
        if (osgiPluginVersion != null) {
            return osgiPluginVersion;
        } else {
            return this.getInstalledNonOsgiPluginVersion(plugin);
        }
    }

    private Collection<String> getInstalledOsgiPluginIds() {
        Collection<String> potentialOsgiPluginIds = new HashSet<>();

        try {
            for (String installedKar : this.getKarService().list()) {
                potentialOsgiPluginIds.add(installedKar);
            }
        } catch (Exception e) {
        }

        for (Feature feature : this.getFeaturesService().listInstalledFeatures()) {
            potentialOsgiPluginIds.add(feature.getName());
        }
        return potentialOsgiPluginIds;
    }

    private Collection<String> getInstalledPluginIds() {
        Collection<String> installedPluginIds = this.getInstalledOsgiPluginIds();
        installedPluginIds.addAll(this.getInstalledNonOsgiPluginIds());

        return installedPluginIds;
    }

    private boolean executeInstall(IPlugin plugin, IPluginVersion version) {
        if (version.isOsgi()) {
            return this.executeOsgiInstall(plugin, version);
        } else {
            // before install, close class loader in case it's a reinstall
            this.unloadPlugin(plugin);
            return this.executeNonOsgiInstall(plugin, version);
        }
    }

    private boolean executeUninstall(IPlugin plugin) {
        IPluginVersion version = this.getInstalledPluginVersion(plugin);
        if (version == null) {
            this.getLogger().debug("Did not find plugin version for installed plugin: " + plugin.getId());
            return false;
        }

        if (version.isOsgi()) {
            return this.executeOsgiUninstall(plugin);
        } else {
            // before install, close class loader in case it's a reinstall
            this.unloadPlugin(plugin);
            return this.executeNonOsgiUninstall(plugin);
        }
    }

    protected abstract boolean hasMarketplacePermission();

    protected abstract void unloadPlugin(IPlugin pluginId);

    protected abstract boolean executeNonOsgiInstall(IPlugin plugin, IPluginVersion version);

    protected abstract boolean executeNonOsgiUninstall(IPlugin plugin);

    protected abstract IPluginVersion getInstalledNonOsgiPluginVersion(IPlugin plugin);

    protected abstract Collection<String> getInstalledNonOsgiPluginIds();
}