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

Java tutorial

Introduction

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

Source

package org.pentaho.marketplace.domain.services;

import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.di.core.Result;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.LogLevel;
import org.pentaho.di.job.Job;
import org.pentaho.di.job.JobMeta;
import org.pentaho.marketplace.domain.model.entities.DevelopmentStage;
import org.pentaho.marketplace.domain.model.entities.interfaces.ICategory;
import org.pentaho.marketplace.domain.model.entities.interfaces.IDevelopmentStage;
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.ICategoryFactory;
import org.pentaho.marketplace.domain.model.factories.interfaces.IDomainStatusMessageFactory;
import org.pentaho.marketplace.domain.model.factories.interfaces.IPluginFactory;
import org.pentaho.marketplace.domain.model.factories.interfaces.IPluginVersionFactory;
import org.pentaho.marketplace.domain.model.factories.interfaces.IVersionDataFactory;
import org.pentaho.marketplace.domain.services.helpers.Util;
import org.pentaho.marketplace.domain.services.interfaces.IPluginService;
import org.pentaho.platform.api.engine.IPluginManager;
import org.pentaho.platform.api.engine.IPluginResourceLoader;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.security.SecurityHelper;
import org.pentaho.platform.util.VersionHelper;
import org.pentaho.platform.util.VersionInfo;
import org.pentaho.platform.util.web.HttpUtil;
import org.pentaho.telemetry.BaPluginTelemetry;
import org.pentaho.telemetry.TelemetryHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class PluginService implements IPluginService {

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

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

    //region Constants
    private static final String UNAUTHORIZED_ACCESS_MESSAGE = "Unauthorized Access. Your Pentaho roles do not allow you to make changes to plugins.";
    private static final String UNAUTHORIZED_ACCESS_ERROR_CODE = "ERROR_0002_UNAUTHORIZED_ACCESS";
    private static final String NO_PLUGIN_ERROR_CODE = "NO_PLUGIN";
    private static final String FAIL_ERROR_CODE = "FAIL";
    private static final String PLUGIN_INSTALLED_CODE = "PLUGIN_INSTALLED";
    private static final String PLUGIN_UNINSTALLED_CODE = "PLUGIN_UNINSTALLED";
    private static final String CLOSE_METHOD_NAME = "close";
    private static final String PLUGIN_NAME = "marketplace";

    private static final String MARKETPLACE_ENTRIES_URL_FALLBACK = "https://raw.github.com/pentaho/marketplace-metadata/master/marketplace.xml";

    // region XML
    // endregion

    //endregion

    //region Attributes
    private Log logger = LogFactory.getLog(this.getClass());
    private XPath xpath;
    private IPluginFactory pluginFactory;
    private IPluginVersionFactory pluginVersionFactory;
    private IVersionDataFactory versionDataFactory;
    private ICategoryFactory categoryFactory;
    private IDomainStatusMessageFactory domainStatusMessageFactory;
    //endregion

    //region Constructors
    @Autowired
    public PluginService(IPluginFactory pluginFactory, IPluginVersionFactory pluginVersionFactory,
            IVersionDataFactory versionDataFactory, ICategoryFactory categoryFactory,
            IDomainStatusMessageFactory domainStatusMessageFactory) {

        //initialize dependencies
        this.pluginFactory = pluginFactory;
        this.pluginVersionFactory = pluginVersionFactory;
        this.versionDataFactory = versionDataFactory;
        this.categoryFactory = categoryFactory;
        this.domainStatusMessageFactory = domainStatusMessageFactory;

        this.xpath = XPathFactory.newInstance().newXPath();
    }
    //endregion

    //region Methods
    private void closeClassLoader(String pluginId) {
        IPluginManager pluginManager = PentahoSystem.get(IPluginManager.class, PentahoSessionHolder.getSession());
        ClassLoader cl = pluginManager.getClassLoader(pluginId);
        if (cl != null && cl instanceof URLClassLoader) {
            try {
                URLClassLoader cl1 = (URLClassLoader) cl;
                Util.closeURLClassLoader(cl1);
                Method closeMethod = cl1.getClass().getMethod(PluginService.CLOSE_METHOD_NAME);
                closeMethod.invoke(cl1);
            } catch (Throwable e) {
                if (e instanceof NoSuchMethodException) {
                    logger.debug("Probably running in java 6 so close method on URLClassLoader is not available");
                } else if (e instanceof IOException) {
                    logger.error("Unable to close class loader for plugin. Will try uninstalling plugin anyway", e);
                } else {
                    logger.error("Error while closing class loader", e);
                }
            }
        }
    }

    private boolean hasMarketplacePermission() {

        Authentication auth = SecurityHelper.getInstance().getAuthentication(PentahoSessionHolder.getSession(),
                true);
        IPluginResourceLoader resLoader = PentahoSystem.get(IPluginResourceLoader.class, null);
        String roles = null;
        String users = null;

        try {
            roles = resLoader.getPluginSetting(getClass(), "settings/marketplace-roles"); //$NON-NLS-1$
            users = resLoader.getPluginSetting(getClass(), "settings/marketplace-users"); //$NON-NLS-1$
        } catch (Exception e) {
            logger.debug("Error getting data access plugin settings", e);
        }

        if (roles == null) {
            // If it's true, we'll just check if the user is admin
            return SecurityHelper.getInstance().isPentahoAdministrator(PentahoSessionHolder.getSession());
        }

        String[] roleArr = roles.split(","); //$NON-NLS-1$

        for (String role : roleArr) {
            for (GrantedAuthority userRole : auth.getAuthorities()) {
                if (role != null && role.trim().equals(userRole.getAuthority())) {
                    return true;
                }
            }
        }
        if (users != null) {
            String[] userArr = users.split(","); //$NON-NLS-1$
            for (String user : userArr) {
                if (user != null && user.trim().equals(auth.getName())) {
                    return true;
                }
            }
        }
        return false;
    }

    private String discoverInstalledVersion(IPlugin plugin) {

        String versionPath = PentahoSystem.getApplicationContext()
                .getSolutionPath("system/" + plugin.getId() + "/version.xml");
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        FileReader reader = null;
        try {
            File file = new File(versionPath);
            if (!file.exists()) {
                return "Unknown";
            }
            DocumentBuilder db = dbf.newDocumentBuilder();
            reader = new FileReader(versionPath);
            Document dom = db.parse(new InputSource(reader));
            NodeList versionElements = dom.getElementsByTagName("version");
            if (versionElements.getLength() >= 1) {
                Element versionElement = (Element) versionElements.item(0);

                plugin.setInstalledBuildId(versionElement.getAttribute("buildId"));
                plugin.setInstalledBranch(versionElement.getAttribute("branch"));
                plugin.setInstalledVersion(versionElement.getTextContent());

                return versionElement.getTextContent();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
                // do nothing
            }
        }
        return "Unknown";
    }

    private Collection<String> getInstalledPluginsFromFileSystem() {

        Collection<String> plugins = new ArrayList<String>();

        File systemDir = new File(PentahoSystem.getApplicationContext().getSolutionPath("system/"));

        String[] dirs = systemDir.list(DirectoryFileFilter.INSTANCE);

        for (int i = 0; i < dirs.length; i++) {
            String dir = dirs[i];
            if ((new File(systemDir.getAbsolutePath() + File.separator + dir + File.separator + "plugin.xml"))
                    .isFile()) {
                plugins.add(dir);
            }
        }

        return plugins;
    }

    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:
        VersionInfo versionInfo = VersionHelper.getVersionInfo(PentahoSystem.class);
        String v = versionInfo.getVersionNumber();
        IVersionData pvMax = this.versionDataFactory.create(pv.getMaxParentVersion());
        IVersionData pvMin = this.versionDataFactory.create(pv.getMinParentVersion());
        IVersionData version = this.versionDataFactory.create(v);

        return version.within(pvMin, pvMax);
    }

    private String getElementChildValue(Element element, String child) throws XPathExpressionException {
        Element childElement = (Element) xpath.evaluate(child, element, XPathConstants.NODE);

        if (childElement != null) {
            return childElement.getTextContent();
        } else {
            return null;
        }
    }

    private String getMarketplaceSiteContent() {
        IPluginResourceLoader resLoader = PentahoSystem.get(IPluginResourceLoader.class, null);
        String site = null;
        try {
            site = resLoader.getPluginSetting(getClass(), "settings/marketplace-site"); //$NON-NLS-1$
        } catch (Exception e) {
            logger.debug("Error getting data access plugin settings", e);
        }

        if (site == null || "".equals(site)) {
            site = MARKETPLACE_ENTRIES_URL_FALLBACK;
        }

        return HttpUtil.getURLContent(site);
    }

    private Collection<IPlugin> loadPluginsFromSite() {
        String content = getMarketplaceSiteContent();
        //Sometimes this call fails. Second attemp is always succesfull
        if (StringUtils.isEmpty(content)) {
            content = getMarketplaceSiteContent();
        }
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document dom = db.parse(new InputSource(new StringReader(content)));
            NodeList plugins = dom.getElementsByTagName("market_entry");
            Collection<IPlugin> pluginList = new ArrayList<IPlugin>();
            for (int i = 0; i < plugins.getLength(); i++) {
                Element pluginElement = (Element) plugins.item(i);
                String type = getElementChildValue(pluginElement, "type");
                if (!"Platform".equals(type)) {
                    continue;
                }

                IPlugin plugin = this.pluginFactory.create();
                plugin.setId(getElementChildValue(pluginElement, "id"));
                plugin.setName(getElementChildValue(pluginElement, "name"));
                plugin.setDescription(getElementChildValue(pluginElement, "description"));

                plugin.setAuthorName(getElementChildValue(pluginElement, "author"));
                plugin.setAuthorUrl(getElementChildValue(pluginElement, "author_url"));
                plugin.setAuthorLogo(getElementChildValue(pluginElement, "author_logo"));
                plugin.setImg(getElementChildValue(pluginElement, "img"));

                plugin.setSmallImg(getElementChildValue(pluginElement, "small_img"));
                plugin.setDocumentationUrl(getElementChildValue(pluginElement, "documentation_url"));
                plugin.setInstallationNotes(getElementChildValue(pluginElement, "installation_notes"));
                plugin.setLicense(getElementChildValue(pluginElement, "license"));
                plugin.setLicenseName(getElementChildValue(pluginElement, "license_name"));
                plugin.setLicenseText(getElementChildValue(pluginElement, "license_text"));
                plugin.setDependencies(getElementChildValue(pluginElement, "dependencies"));
                plugin.setCategory(this.getCategory(pluginElement));

                NodeList availableVersions = (NodeList) this.xpath.evaluate("versions/version", pluginElement,
                        XPathConstants.NODESET);

                if (availableVersions.getLength() > 0) {
                    Collection<IPluginVersion> versions = new ArrayList<IPluginVersion>();
                    for (int j = 0; j < availableVersions.getLength(); j++) {
                        Element versionElement = (Element) availableVersions.item(j);
                        IPluginVersion pv = this.pluginVersionFactory.create();
                        pv.setBranch(getElementChildValue(versionElement, "branch"));
                        pv.setName(getElementChildValue(versionElement, "name"));
                        pv.setVersion(getElementChildValue(versionElement, "version"));
                        pv.setDownloadUrl(getElementChildValue(versionElement, "package_url"));
                        pv.setSamplesDownloadUrl(getElementChildValue(versionElement, "samples_url"));
                        pv.setDescription(getElementChildValue(versionElement, "description"));
                        pv.setChangelog(getElementChildValue(versionElement, "changelog"));
                        pv.setBuildId(getElementChildValue(versionElement, "build_id"));
                        pv.setReleaseDate(getElementChildValue(versionElement, "releaseDate"));
                        pv.setMinParentVersion(getElementChildValue(versionElement, "min_parent_version"));
                        pv.setMaxParentVersion(getElementChildValue(versionElement, "max_parent_version"));
                        pv.setDevelopmentStage(getDevelopmentStage(versionElement));

                        if (withinParentVersion(pv)) {
                            versions.add(pv);
                        }

                    }
                    plugin.setVersions(versions);
                }

                NodeList availableScreenshots = (NodeList) xpath.evaluate("screenshots/screenshot", pluginElement,
                        XPathConstants.NODESET);
                if (availableScreenshots.getLength() > 0) {
                    String[] screenshots = new String[availableScreenshots.getLength()];

                    for (int j = 0; j < availableScreenshots.getLength(); j++) {
                        Element screenshotElement = (Element) availableScreenshots.item(j);
                        screenshots[j] = screenshotElement.getTextContent();
                    }

                    plugin.setScreenshots(screenshots);
                }

                // only include plugins that have versions within this release
                if (plugin.getVersions() != null && plugin.getVersions().size() > 0) {
                    pluginList.add(plugin);
                }
            }
            return pluginList;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private ICategory getCategory(Element pluginElement) throws XPathExpressionException {
        final String CATEGORY_ELEMENT_NAME = "category";

        Element categoryElement = (Element) xpath.evaluate(CATEGORY_ELEMENT_NAME, pluginElement,
                XPathConstants.NODE);
        if (categoryElement == null) {
            return null;
        }

        return this.getCategoryFromCategoryElement(categoryElement);
    }

    private ICategory getCategoryFromCategoryElement(Element categoryElement) throws XPathExpressionException {

        final String PARENT_ELEMENT_NAME = "parent";
        final String NAME_ELEMENT_NAME = "name";

        ICategory parent = null;
        Element parentElement = (Element) xpath.evaluate(PARENT_ELEMENT_NAME, categoryElement, XPathConstants.NODE);
        if (parentElement != null) {
            parent = getCategoryFromCategoryElement(parentElement);
        }

        String name = getElementChildValue(categoryElement, NAME_ELEMENT_NAME);
        ICategory category = this.categoryFactory.create(name, parent);
        return category;
    }

    /**
     * Parses the version element to get the development stage
     * @param versionElement where the development stage element is contained
     * @return
     */
    private IDevelopmentStage getDevelopmentStage(Element versionElement) throws XPathExpressionException {
        final String DEVELOPMENT_STAGE_ELEMENT_NAME = "development_stage";
        final String DEVELOPMENT_STAGE_LANE_ELEMENT_NAME = "lane";
        final String DEVELOPMENT_STAGE_PHASE_ELEMENT_NAME = "phase";

        Element devStageElement = (Element) xpath.evaluate(DEVELOPMENT_STAGE_ELEMENT_NAME, versionElement,
                XPathConstants.NODE);
        if (devStageElement == null) {
            return null;
        }

        String lane = this.getElementChildValue(devStageElement, DEVELOPMENT_STAGE_LANE_ELEMENT_NAME);
        String phase = this.getElementChildValue(devStageElement, DEVELOPMENT_STAGE_PHASE_ELEMENT_NAME);
        ;

        // TODO: switch to factory to allow DI?
        return new DevelopmentStage(lane, phase);
    }

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

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

        Iterable<IPlugin> plugins = getPlugins();
        IPlugin toInstall = null;
        for (IPlugin plugin : plugins) {
            if (plugin.getId().equals(pluginId)) {
                toInstall = plugin;
            }
        }
        if (toInstall == null) {
            return this.domainStatusMessageFactory.create(PluginService.NO_PLUGIN_ERROR_CODE, "Plugin Not Found");
        }

        // 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
        if (toInstall.getId().indexOf(".") >= 0) {
            return this.domainStatusMessageFactory.create(PluginService.NO_PLUGIN_ERROR_CODE,
                    "Plugin ID contains an illegal character");
        }

        // before deletion, close class loader
        this.closeClassLoader(toInstall.getId());

        String downloadUrl, samplesDownloadUrl, availableVersion;

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

        // get plugin path
        String jobPath = PentahoSystem.getApplicationContext().getSolutionPath(
                "system/" + PluginService.PLUGIN_NAME + "/processes/download_and_install_plugin.kjb");

        try {
            JobMeta installJobMeta = new JobMeta(jobPath, null);
            Job job = new Job(null, installJobMeta);

            File file = new File(
                    PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/downloads"));
            file.mkdirs();
            file = new File(PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/backups"));
            file.mkdirs();
            file = new File(PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/staging"));
            file.mkdirs();

            job.getJobMeta().setParameterValue("downloadUrl", downloadUrl);
            if (toInstall.getVersionByBranch(versionBranch).getSamplesDownloadUrl() != null) {
                job.getJobMeta().setParameterValue("samplesDownloadUrl", samplesDownloadUrl);
                job.getJobMeta().setParameterValue("samplesDir", "/public/plugin-samples");
                job.getJobMeta().setParameterValue("samplesTargetDestination", PentahoSystem.getApplicationContext()
                        .getSolutionPath("plugin-samples/" + toInstall.getId()));
                job.getJobMeta().setParameterValue("samplesTargetBackup",
                        PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/backups/"
                                + toInstall.getId() + "_samples_" + new Date().getTime()));
                job.getJobMeta().setParameterValue("samplesDownloadDestination",
                        PentahoSystem.getApplicationContext()
                                .getSolutionPath("system/plugin-cache/downloads/" + toInstall.getId() + "-samples-"
                                        + availableVersion + "_" + new Date().getTime() + ".zip"));
                job.getJobMeta().setParameterValue("samplesStagingDestination", PentahoSystem
                        .getApplicationContext().getSolutionPath("system/plugin-cache/staging_samples"));
                job.getJobMeta().setParameterValue("samplesStagingDestinationAndDir",
                        PentahoSystem.getApplicationContext()
                                .getSolutionPath("system/plugin-cache/staging_samples/" + toInstall.getId()));
            }
            job.getJobMeta().setParameterValue("downloadDestination",
                    PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/downloads/"
                            + toInstall.getId() + "-" + availableVersion + "_" + new Date().getTime() + ".zip"));
            job.getJobMeta().setParameterValue("stagingDestination",
                    PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/staging"));
            job.getJobMeta().setParameterValue("stagingDestinationAndDir", PentahoSystem.getApplicationContext()
                    .getSolutionPath("system/plugin-cache/staging/" + toInstall.getId()));
            job.getJobMeta().setParameterValue("targetDestination",
                    PentahoSystem.getApplicationContext().getSolutionPath("system/" + toInstall.getId()));
            job.getJobMeta().setParameterValue("targetBackup",
                    PentahoSystem.getApplicationContext().getSolutionPath(
                            "system/plugin-cache/backups/" + toInstall.getId() + "_" + new Date().getTime()));

            job.copyParametersFrom(job.getJobMeta());
            job.setLogLevel(LogLevel.DETAILED);
            job.activateParameters();
            job.start();
            job.waitUntilFinished();
            Result result = job.getResult(); // Execute the selected job.

            if (result == null || result.getNrErrors() > 0) {
                return this.domainStatusMessageFactory.create(PluginService.FAIL_ERROR_CODE,
                        "Failed to execute install, see log for details.");
            }
        } catch (KettleException e) {
            logger.error(e.getMessage(), e);
            return this.domainStatusMessageFactory.create(PluginService.FAIL_ERROR_CODE,
                    "Failed to execute install, see log for details.");
        }

        BaPluginTelemetry telemetryEvent = new BaPluginTelemetry(PLUGIN_NAME);
        Map<String, String> extraInfo = new HashMap<String, String>(1);
        extraInfo.put("installedPlugin", toInstall.getId());
        extraInfo.put("installedVersion", availableVersion);
        extraInfo.put("installedBranch", versionBranch);

        telemetryEvent.sendTelemetryRequest(TelemetryHelper.TelemetryEventType.INSTALLATION, extraInfo);

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

    private IDomainStatusMessage uninstallPluginAux(String pluginId) throws MarketplaceSecurityException {

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

        Iterable<IPlugin> plugins = getPlugins();
        IPlugin toUninstall = null;

        for (IPlugin plugin : plugins) {
            if (plugin.getId().equals(pluginId)) {
                toUninstall = plugin;
            }
        }
        if (toUninstall == null) {
            return this.domainStatusMessageFactory.create(PluginService.NO_PLUGIN_ERROR_CODE, "Plugin Not Found");
        }

        // before deletion, close class loader
        this.closeClassLoader(toUninstall.getId());

        String versionBranch = toUninstall.getInstalledBranch();
        // get plugin path
        String jobPath = PentahoSystem.getApplicationContext()
                .getSolutionPath("system/" + PLUGIN_NAME + "/processes/uninstall_plugin.kjb");

        try {
            JobMeta uninstallJobMeta = new JobMeta(jobPath, null);
            Job job = new Job(null, uninstallJobMeta);

            File file = new File(
                    PentahoSystem.getApplicationContext().getSolutionPath("system/plugin-cache/backups"));
            file.mkdirs();

            String uninstallBackup = PentahoSystem.getApplicationContext().getSolutionPath(
                    "system/plugin-cache/backups/" + toUninstall.getId() + "_" + new Date().getTime());
            job.getJobMeta().setParameterValue("uninstallLocation",
                    PentahoSystem.getApplicationContext().getSolutionPath("system/" + toUninstall.getId()));
            job.getJobMeta().setParameterValue("uninstallBackup", uninstallBackup);
            job.getJobMeta().setParameterValue("samplesDir", "/public/plugin-samples/" + toUninstall.getId());

            job.copyParametersFrom(job.getJobMeta());
            job.activateParameters();
            job.start();
            job.waitUntilFinished();
            Result result = job.getResult(); // Execute the selected job.

            if (result == null || result.getNrErrors() > 0) {
                return this.domainStatusMessageFactory.create(PluginService.FAIL_ERROR_CODE,
                        "Failed to execute uninstall, see log for details.");
            }
        } catch (KettleException e) {
            logger.error(e.getMessage(), e);
        }

        BaPluginTelemetry telemetryEvent = new BaPluginTelemetry(PLUGIN_NAME);
        Map<String, String> extraInfo = new HashMap<String, String>(1);
        extraInfo.put("uninstalledPlugin", toUninstall.getId());
        extraInfo.put("uninstalledPluginVersion", toUninstall.getInstalledVersion());
        extraInfo.put("uninstalledPluginBranch", toUninstall.getInstalledBranch());
        telemetryEvent.sendTelemetryRequest(TelemetryHelper.TelemetryEventType.REMOVAL, extraInfo);

        return this.domainStatusMessageFactory.create(PluginService.PLUGIN_UNINSTALLED_CODE,
                toUninstall.getName() + " was successfully uninstalled.  Please restart your BI Server.");
    }
    //endregion

    //region IPluginService implementation
    @Override
    public Collection<IPlugin> getPlugins() {
        Collection<IPlugin> plugins = loadPluginsFromSite();

        // There are 2 methods of doing this.
        // 1 ) Plugin manager
        // 2 ) Scan file system
        // We'll use 2 ) because 1 ) gets totally screwed up after we do an install/uninstall operation

        // List<String> installedPlugins = getInstalledPluginsFromPluginManager(  );
        Collection<String> installedPlugins = getInstalledPluginsFromFileSystem();

        if (installedPlugins.size() > 0) {
            Map<String, IPlugin> marketplacePlugins = new HashMap<String, IPlugin>();
            if (plugins != null) {
                for (IPlugin plugin : plugins) {
                    marketplacePlugins.put(plugin.getId(), plugin);
                }
            }

            for (String installedPlugin : installedPlugins) {
                IPlugin plugin = marketplacePlugins.get(installedPlugin);
                if (plugin != null) {
                    plugin.setInstalled(true);
                    discoverInstalledVersion(plugin);
                }
            }
        }
        return plugins;
    }

    @Override
    public IDomainStatusMessage installPlugin(String pluginId, String versionBranch) {

        try {
            IDomainStatusMessage msg = this.installPluginAux(pluginId, versionBranch);
            return msg;
        } catch (MarketplaceSecurityException e) {
            logger.debug(e.getMessage(), e);
            return this.domainStatusMessageFactory.create(PluginService.UNAUTHORIZED_ACCESS_ERROR_CODE,
                    PluginService.UNAUTHORIZED_ACCESS_MESSAGE);
        }
    }

    @Override
    public IDomainStatusMessage uninstallPlugin(String pluginId) {

        try {
            IDomainStatusMessage msg = uninstallPluginAux(pluginId);
            return msg;
        } catch (MarketplaceSecurityException e) {
            logger.debug(e.getMessage(), e);
            return this.domainStatusMessageFactory.create(PluginService.UNAUTHORIZED_ACCESS_ERROR_CODE,
                    PluginService.UNAUTHORIZED_ACCESS_MESSAGE);
        }
    }
    //endregion
}