org.springframework.roo.addon.roobot.eclipse.client.AddOnRooBotEclipseOperationsImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.roo.addon.roobot.eclipse.client.AddOnRooBotEclipseOperationsImpl.java

Source

/*******************************************************************************
 *  Copyright (c) 2012 VMware, Inc.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  Contributors:
 *      VMware, Inc. - initial API and implementation
 *******************************************************************************/
package org.springframework.roo.addon.roobot.eclipse.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.zip.ZipInputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.roobot.client.model.Bundle;
import org.springframework.roo.addon.roobot.client.model.BundleVersion;
import org.springframework.roo.addon.roobot.client.model.Comment;
import org.springframework.roo.addon.roobot.client.model.Rating;
import org.springframework.roo.felix.BundleSymbolicName;
import org.springframework.roo.felix.pgp.PgpKeyId;
import org.springframework.roo.felix.pgp.PgpService;
import org.springframework.roo.shell.Shell;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.springframework.roo.uaa.UaaRegistrationService;
import org.springframework.roo.url.stream.UrlInputStreamService;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.Plugin;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginService.InstallOrUpgradeStatus;
import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginVersion;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Implementation of commands that are available via the Roo shell.
 * 
 * @author Stefan Schmidt
 * @author Ben Alex
 * @since 1.1
 */
@Component
@Service
public class AddOnRooBotEclipseOperationsImpl implements AddOnRooBotEclipseOperations {

    private Map<String, Bundle> bundleCache;
    @Reference
    private Shell shell;
    @Reference
    private PgpService pgpService;
    @Reference
    private UrlInputStreamService urlInputStreamService;
    private static final Logger log = Logger.getLogger(AddOnRooBotEclipseOperationsImpl.class.getName());
    private Properties props;
    private ComponentContext context;
    private static String ROOBOT_XML_URL = "http://spring-roo-repository.springsource.org/roobot/roobot.xml.zip";
    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    private final Class<AddOnRooBotEclipseOperationsImpl> mutex = AddOnRooBotEclipseOperationsImpl.class;
    //   private Preferences prefs;

    public static final String ADDON_UPGRADE_STABILITY_LEVEL = "ADDON_UPGRADE_STABILITY_LEVEL";

    protected void activate(ComponentContext context) {
        this.context = context;
        //prefs = Preferences.userNodeForPackage(AddOnRooBotEclipseOperationsImpl.class);
        bundleCache = new HashMap<String, Bundle>();
        Thread t = new Thread(new Runnable() {
            public void run() {
                synchronized (mutex) {
                    populateBundleCache(true);
                }
            }
        }, "Spring Roo RooBot Add-In Index Eager Download");
        t.start();
        props = new Properties();
        try {
            props.load(FileUtils.getInputStream(getClass(), "manager.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean trust(PluginVersion pluginVersion) {
        BundleVersion bundleVersion = ((RooAddOnVersion) pluginVersion).getBundleVersion();
        pgpService.trust(new PgpKeyId(bundleVersion.getPgpKey()));
        return true;
    }

    public InstallOrUpgradeStatus installOrUpgradeAddOn(PluginVersion pluginVersion, boolean install) {
        synchronized (mutex) {
            BundleVersion bundleVersion = ((RooAddOnVersion) pluginVersion).getBundleVersion();
            Bundle bundle = ((RooAddOnVersion) pluginVersion).getBundle();
            if (!verifyRepository(bundleVersion.getObrUrl())) {
                return InstallOrUpgradeStatus.INVALID_REPOSITORY_URL;
            }
            boolean success = true;
            int count = countBundles();
            boolean requiresWrappedCoreDep = bundleVersion.getDescription().contains("#wrappedCoreDependency");
            if (requiresWrappedCoreDep && !shell.executeCommand(
                    "osgi obr url add --url http://spring-roo-repository.springsource.org/repository.xml")) {
                success = false;
            }
            if (!shell.executeCommand("osgi obr url add --url " + bundleVersion.getObrUrl())) {
                success = false;
            }
            if (!shell.executeCommand("osgi obr start --bundleSymbolicName " + bundle.getSymbolicName())) {
                success = false;
            }
            if (!shell.executeCommand("osgi obr url remove --url " + bundleVersion.getObrUrl())) {
                success = false;
            }
            if (requiresWrappedCoreDep && !shell.executeCommand(
                    "osgi obr url remove --url http://spring-roo-repository.springsource.org/repository.xml")) {
                success = false;
            }
            if (install && count == countBundles()) {
                return InstallOrUpgradeStatus.VERIFICATION_NEEDED; // most likely PgP verification required before the bundle can be installed, no log needed
            }

            if (success) {
                return InstallOrUpgradeStatus.SUCCESS;
            } else {
                return InstallOrUpgradeStatus.FAILED;
            }
        }
    }

    public InstallOrUpgradeStatus removeAddOn(PluginVersion pluginVersion) {
        Bundle bundle = ((RooAddOnVersion) pluginVersion).getBundle();
        BundleSymbolicName bsn = new BundleSymbolicName(bundle.getSymbolicName());
        synchronized (mutex) {
            Validate.notNull(bsn, "Bundle symbolic name required");
            boolean success = false;
            int count = countBundles();
            success = shell.executeCommand("osgi uninstall --bundleSymbolicName " + bsn.getKey());
            if (count == countBundles() || !success) {
                return InstallOrUpgradeStatus.FAILED;
            } else {
                return InstallOrUpgradeStatus.SUCCESS;
            }
        }
    }

    public List<Plugin> searchAddOns(String searchTerms, boolean refresh, boolean trustedOnly,
            boolean compatibleOnly, String requiresCommand) {
        synchronized (mutex) {
            if (bundleCache.size() == 0) {
                // We should refresh regardless in this case
                refresh = true;
            }
            if (refresh && populateBundleCache(false)) {
            }
            if (bundleCache.size() != 0) {
                boolean onlyRelevantBundles = false;
                if (searchTerms != null && !"".equals(searchTerms)) {
                    onlyRelevantBundles = true;
                    String[] terms = searchTerms.split(",");
                    for (Bundle bundle : bundleCache.values()) {
                        //first set relevance of all bundles to zero
                        bundle.setSearchRelevance(0f);
                        int hits = 0;
                        BundleVersion latest = bundle.getLatestVersion();
                        for (String term : terms) {
                            if ((bundle.getSymbolicName() + ";" + latest.getSummary()).toLowerCase()
                                    .contains(term.trim().toLowerCase()) || term.equals("*")) {
                                hits++;
                            }
                        }
                        bundle.setSearchRelevance(hits / terms.length);
                    }
                }
                List<Bundle> bundles = Bundle.orderBySearchRelevance(new ArrayList<Bundle>(bundleCache.values()));
                LinkedList<Bundle> filteredSearchResults = filterList(bundles, trustedOnly, compatibleOnly,
                        requiresCommand, onlyRelevantBundles);
                return convertToAddOns(filteredSearchResults);
            }
            return null;
        }
    }

    private List<Plugin> convertToAddOns(LinkedList<Bundle> filteredSearchResults) {
        BundleContext bc = context.getBundleContext();
        org.osgi.framework.Bundle[] bundles = bc.getBundles();
        Map<String, org.osgi.framework.Bundle> installedBundleBySymbolicName = new HashMap<String, org.osgi.framework.Bundle>();
        for (org.osgi.framework.Bundle bundle : bundles) {
            installedBundleBySymbolicName.put(bundle.getSymbolicName(), bundle);
        }

        ArrayList<Plugin> result = new ArrayList<Plugin>();
        for (Bundle bundle : filteredSearchResults) {
            org.osgi.framework.Bundle installedBundle = installedBundleBySymbolicName.get(bundle.getSymbolicName());

            // create add-on for each bundle
            Plugin plugin = new Plugin(bundle.getSymbolicName());

            // add all available versions
            List<BundleVersion> versions = BundleVersion.orderByVersion(bundle.getVersions());
            for (BundleVersion version : versions) {
                RooAddOnVersion addOnVersion = new RooAddOnVersion(bundle, version);
                addOnVersion.setTitle(version.getPresentationName());
                addOnVersion.setVersion(version.getVersion());
                addOnVersion.setDescription(version.getDescription());
                addOnVersion.setRuntimeVersion(version.getRooVersion());
                // name needs to match between bundle and version
                addOnVersion.setName(plugin.getName());

                if (installedBundle != null
                        && installedBundle.getVersion().toString().equals(version.getVersion())) {
                    addOnVersion.setInstalled(true);
                }

                plugin.addVersion(addOnVersion);
                plugin.setLatestReleasedVersion(addOnVersion);
            }

            result.add(plugin);
        }
        return result;
    }

    //   public void upgradeSettings(AddOnStabilityLevel addOnStabilityLevel) {
    //      if (addOnStabilityLevel == null) {
    //         addOnStabilityLevel = checkAddOnStabilityLevel(addOnStabilityLevel);
    //         log.info("Current Add-on Stability Level: " + addOnStabilityLevel.name());
    //      } else {
    //         boolean success = true;
    //         prefs.putInt(ADDON_UPGRADE_STABILITY_LEVEL, addOnStabilityLevel.getLevel());
    //         try {
    //            prefs.flush();
    //         } catch (BackingStoreException ignore) {
    //            success = false;
    //         }
    //         if (success) {
    //            log.info("Add-on Stability Level: " + addOnStabilityLevel.name() + " stored");
    //         } else {
    //            log.warning("Unable to store add-on stability level at this time");
    //         }
    //      }
    //   }

    public Map<String, Bundle> getAddOnCache(boolean refresh) {
        synchronized (mutex) {
            if (refresh) {
                populateBundleCache(false);
            }
            return Collections.unmodifiableMap(bundleCache);
        }
    }

    private LinkedList<Bundle> filterList(List<Bundle> bundles, boolean trustedOnly, boolean compatibleOnly,
            String requiresCommand, boolean onlyRelevantBundles) {
        LinkedList<Bundle> filteredList = new LinkedList<Bundle>();
        List<PGPPublicKeyRing> keys = null;
        if (trustedOnly) {
            keys = pgpService.getTrustedKeys();
        }
        bundle_loop: for (Bundle bundle : bundles) {
            BundleVersion latest = bundle.getLatestVersion();
            if (onlyRelevantBundles && !(bundle.getSearchRelevance() > 0)) {
                continue bundle_loop;
            }
            if (trustedOnly && !isTrustedKey(keys, latest.getPgpKey())) {
                continue bundle_loop;
            }
            if (compatibleOnly && !isCompatible(latest.getRooVersion())) {
                continue bundle_loop;
            }
            if (requiresCommand != null && requiresCommand.length() > 0) {
                boolean matchingCommand = false;
                for (String cmd : latest.getCommands().keySet()) {
                    if (cmd.startsWith(requiresCommand) || requiresCommand.startsWith(cmd)) {
                        matchingCommand = true;
                        break;
                    }
                }
                if (!matchingCommand) {
                    continue bundle_loop;
                }
            }
            filteredList.add(bundle);
        }
        return filteredList;
    }

    @SuppressWarnings("unchecked")
    private boolean isTrustedKey(List<PGPPublicKeyRing> keys, String keyId) {
        for (PGPPublicKeyRing keyRing : keys) {
            Iterator<PGPPublicKey> it = keyRing.getPublicKeys();
            while (it.hasNext()) {
                PGPPublicKey pgpKey = (PGPPublicKey) it.next();
                if (new PgpKeyId(pgpKey).equals(new PgpKeyId(keyId))) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean populateBundleCache(boolean startupTime) {
        boolean success = false;
        InputStream is = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            String url = props.getProperty("roobot.url", ROOBOT_XML_URL);
            if (url == null) {
                log.warning("Bundle properties could not be loaded");
                return false;
            }
            if (url.startsWith("http://")) {
                // Handle it as HTTP
                URL httpUrl = new URL(url);
                String failureMessage = urlInputStreamService.getUrlCannotBeOpenedMessage(httpUrl);
                if (failureMessage != null) {
                    if (!startupTime) {
                        // This wasn't just an eager startup time attempt, so let's display the error reason
                        // (for startup time, we just fail quietly)
                        log.warning(failureMessage);
                    }
                    return false;
                }
                // It appears we can acquire the URL, so let's do it
                is = urlInputStreamService.openConnection(httpUrl);
            } else {
                // Fallback to normal protocol handler (likely in local development testing etc
                is = new URL(url).openStream();
            }
            if (is == null) {
                log.warning("Could not connect to Roo Addon bundle repository index");
                return false;
            }

            ZipInputStream zip = new ZipInputStream(is);
            zip.getNextEntry();

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[8192];
            int length = -1;
            while (zip.available() > 0) {
                length = zip.read(buffer, 0, 8192);
                if (length > 0) {
                    baos.write(buffer, 0, length);
                }
            }

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            Document roobotXml = db.parse(bais);

            if (roobotXml != null) {
                bundleCache.clear();
                for (Element bundleElement : XmlUtils.findElements("/roobot/bundles/bundle",
                        roobotXml.getDocumentElement())) {
                    String bsn = bundleElement.getAttribute("bsn");
                    List<Comment> comments = new LinkedList<Comment>();
                    for (Element commentElement : XmlUtils.findElements("comments/comment", bundleElement)) {
                        comments.add(new Comment(Rating.fromInt(new Integer(commentElement.getAttribute("rating"))),
                                commentElement.getAttribute("comment"),
                                dateFormat.parse(commentElement.getAttribute("date"))));
                    }
                    Bundle bundle = new Bundle(bundleElement.getAttribute("bsn"),
                            new Float(bundleElement.getAttribute("uaa-ranking")).floatValue(), comments);

                    for (Element versionElement : XmlUtils.findElements("versions/version", bundleElement)) {
                        if (bsn != null && bsn.length() > 0 && versionElement != null) {
                            String signedBy = "";
                            String pgpKey = versionElement.getAttribute("pgp-key-id");
                            if (pgpKey != null && pgpKey.length() > 0) {
                                Element pgpSigned = XmlUtils.findFirstElement(
                                        "/roobot/pgp-keys/pgp-key[@id='" + pgpKey + "']/pgp-key-description",
                                        roobotXml.getDocumentElement());
                                if (pgpSigned != null) {
                                    signedBy = pgpSigned.getAttribute("text");
                                }
                            }

                            Map<String, String> commands = new HashMap<String, String>();
                            for (Element shell : XmlUtils.findElements("shell-commands/shell-command",
                                    versionElement)) {
                                commands.put(shell.getAttribute("command"), shell.getAttribute("help"));
                            }

                            StringBuilder versionBuilder = new StringBuilder();
                            versionBuilder.append(versionElement.getAttribute("major")).append(".")
                                    .append(versionElement.getAttribute("minor"));
                            String versionMicro = versionElement.getAttribute("micro");
                            if (versionMicro != null && versionMicro.length() > 0) {
                                versionBuilder.append(".").append(versionMicro);
                            }
                            String versionQualifier = versionElement.getAttribute("qualifier");
                            if (versionQualifier != null && versionQualifier.length() > 0) {
                                versionBuilder.append(".").append(versionQualifier);
                            }

                            String rooVersion = versionElement.getAttribute("roo-version");
                            if (rooVersion.equals("*") || rooVersion.length() == 0) {
                                rooVersion = getVersionForCompatibility();
                            } else {
                                String[] split = rooVersion.split("\\.");
                                if (split.length > 2) {
                                    //only interested in major.minor
                                    rooVersion = split[0] + "." + split[1];
                                }
                            }

                            BundleVersion version = new BundleVersion(versionElement.getAttribute("url"),
                                    versionElement.getAttribute("obr-url"), versionBuilder.toString(),
                                    versionElement.getAttribute("name"),
                                    new Long(versionElement.getAttribute("size")).longValue(),
                                    versionElement.getAttribute("description"), pgpKey, signedBy, rooVersion,
                                    commands);
                            // For security reasons we ONLY accept httppgp:// add-on versions
                            if (!version.getUri().startsWith("httppgp://")) {
                                continue;
                            }
                            bundle.addVersion(version);
                        }
                        bundleCache.put(bsn, bundle);
                    }
                }
                success = true;
            }
            zip.close();
            baos.close();
            bais.close();
        } catch (Throwable ignore) {
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException ignored) {
            }
        }
        if (success && startupTime) {
            //printAddonStats();
        }
        return success;
    }

    private int countBundles() {
        BundleContext bc = context.getBundleContext();
        if (bc != null) {
            org.osgi.framework.Bundle[] bundles = bc.getBundles();
            if (bundles != null) {
                return bundles.length;
            }
        }
        return 0;
    }

    private boolean verifyRepository(String repoUrl) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        Document doc = null;
        try {
            URL obrUrl = null;
            obrUrl = new URL(repoUrl);
            DocumentBuilder db = dbf.newDocumentBuilder();
            if (obrUrl.toExternalForm().endsWith(".zip")) {
                ZipInputStream zip = new ZipInputStream(obrUrl.openStream());
                zip.getNextEntry();

                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[8192];
                int length = -1;
                while (zip.available() > 0) {
                    length = zip.read(buffer, 0, 8192);
                    if (length > 0) {
                        baos.write(buffer, 0, length);
                    }
                }
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                doc = db.parse(bais);
            } else {
                doc = db.parse(obrUrl.openStream());
            }
            Validate.notNull(doc, "RooBot was unable to parse the repository document of this add-on");
            for (Element resource : XmlUtils.findElements("resource", doc.getDocumentElement())) {
                if (resource.hasAttribute("uri")) {
                    if (!resource.getAttribute("uri").startsWith("httppgp")) {
                        log.warning("Sorry, the resource " + resource.getAttribute("uri")
                                + " does not follow HTTPPGP conventions mangraded by Spring Roo so the OBR file at "
                                + repoUrl + " is unacceptable at this time");
                        return false;
                    }
                }
            }
            doc = null;
        } catch (Exception e) {
            throw new IllegalStateException("RooBot was unable to parse the repository document of this add-on", e);
        }
        return true;
    }

    //   private AddOnStabilityLevel checkAddOnStabilityLevel(AddOnStabilityLevel addOnStabilityLevel) {
    //      if (addOnStabilityLevel == null) {
    //         addOnStabilityLevel = AddOnStabilityLevel.fromLevel(prefs.getInt(ADDON_UPGRADE_STABILITY_LEVEL, /* default */ AddOnStabilityLevel.RELEASE.getLevel()));
    //      }
    //      return addOnStabilityLevel;
    //   }

    private boolean isCompatible(String version) {
        return version.equals(getVersionForCompatibility());
    }

    private String getVersionForCompatibility() {
        return UaaRegistrationService.SPRING_ROO.getMajorVersion() + "."
                + UaaRegistrationService.SPRING_ROO.getMinorVersion();
    }

}