package org.springframework.roo.obr.addon.search; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.Validate; import org.apache.felix.bundlerepository.Capability; import org.apache.felix.bundlerepository.Repository; import org.apache.felix.bundlerepository.RepositoryAdmin; import org.apache.felix.bundlerepository.Resource; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; import org.osgi.service.component.ComponentContext; import org.springframework.roo.felix.BundleSymbolicName; import org.springframework.roo.obr.addon.search.model.ObrBundle; import org.springframework.roo.shell.Shell; import org.springframework.roo.support.logging.HandlerUtils; /** * * AddonSearch implementation that search available addons on installed OBR * Repositories using OSGi Services * * @author Juan Carlos GarcĂ­a * @since 2.0.0 */ @Component @Service public class ObrAddonSearchOperationsImpl implements ObrAddOnSearchOperations { private static final String CAPABILITY_COMMANDS_NAME = "roo-addon"; private static final String CAPABILITY_JDBCDRIVER_NAME = "jdbcdriver"; private static final String CAPABILITY_LIBRARY_NAME = "library"; private BundleContext context; private static final Logger LOGGER = HandlerUtils.getLogger(ObrAddonSearchOperationsImpl.class); private RepositoryAdmin repositoryAdmin; private Shell shell; private final Object mutex = new Object(); private List<Repository> repositories; private List<ObrBundle> bundlesToInstall; private Map<String, ObrBundle> bundleCache; private Map<String, ObrBundle> searchResultCache; protected void activate(final ComponentContext cContext) { context = cContext.getBundleContext(); repositories = new ArrayList<Repository>(); bundlesToInstall = new ArrayList<ObrBundle>(); bundleCache = new HashMap<String, ObrBundle>(); searchResultCache = new HashMap<String, ObrBundle>(); // Add default repositories addDefaultRepositories(); // Populate Repositories populateRepositories(); // Populate Bundle Cache populateBundleCache(); } @Override public Integer searchAddOns(String searchTerms, SearchType type) { final List<ObrBundle> result = findAddons(searchTerms, type); return result != null ? result.size() : null; } public List<ObrBundle> findAddons(final String searchTerms, final SearchType type) { synchronized (mutex) { LOGGER .log(Level.INFO, String.format("Searching '%s' on installed repositories", searchTerms)); // Populating Repositories populateRepositories(); if (repositories.isEmpty()) { LOGGER.log(Level.INFO, "No repositories installed on Spring Roo yet"); return bundlesToInstall; } // Loading bundles to install depending of search type populateBundlesToInstall(searchTerms, type); // Showing info about matches found if (bundlesToInstall.isEmpty()) { LOGGER.log(Level.INFO, String.format("0 matches found with '%s' on installed repositories", searchTerms)); return bundlesToInstall; } LOGGER.log( Level.INFO, String.format("%s matches found with '%s' on installed repositories", bundlesToInstall.size(), searchTerms)); // Showing list about how to install bundles printResultList(bundlesToInstall); // Refreshing available bundles populateBundleCache(); } return bundlesToInstall; } private void addDefaultRepositories() { // Getting wrapping repository url String wrappingRepoUrl = context.getProperty("wrapping.repository.url"); try { getRepositoryAdmin().addRepository(wrappingRepoUrl); } catch (Exception e) { LOGGER.log(Level.WARNING, "WARNING: Wrapping repository could not be installed"); } } /** * Method to populate current Repositories using OSGi Serive */ private void populateRepositories() { // Cleaning Repositories repositories.clear(); // Validating that RepositoryAdmin exists Validate.notNull(getRepositoryAdmin(), "RepositoryAdmin not found"); for (Repository repo : getRepositoryAdmin().listRepositories()) { repositories.add(repo); } } /** * Method to populate bundles to install. Depending of the search type, will be displayed * different kinds of bundles. * @param requiresCommand * @param type */ private void populateBundlesToInstall(String searchTerms, SearchType type) { // Refreshing Repositories populateRepositories(); // Cleaning Bundles to install bundlesToInstall.clear(); // Cleaning previous search searchResultCache.clear(); int bundleId = 0; for (Repository repo : repositories) { // Getting all resources from every repo Resource[] repoResources = repo.getResources(); for (Resource repoResource : repoResources) { // Creating bundle of current resource ObrBundle bundle = new ObrBundle(repoResource.getSymbolicName(), repoResource.getPresentationName(), repoResource.getSize(), repoResource.getVersion(), repoResource.getURI()); // If current bundle is installed, continue with the next one if (checkIfBundleIsInstalled(bundle)) { continue; } // Getting Resource Capabilites Capability[] resourceCapabilities = repoResource.getCapabilities(); for (Capability capability : resourceCapabilities) { // Depending of search type, is necessary to look for different // capabilities if (type.equals(SearchType.ADDON)) { // Getting resource commands if (capability.getName().equals(CAPABILITY_COMMANDS_NAME)) { // Getting all resource properties Map<String, Object> capabilityProperties = capability.getPropertiesAsMap(); boolean match = false; for (Entry capabilityProperty : capabilityProperties.entrySet()) { String capabilityCommand = (String) capabilityProperty.getValue(); bundle.addCommand(capabilityCommand); if (capabilityCommand.startsWith(searchTerms)) { match = true; } } if (match) { bundleId++; bundlesToInstall.add(bundle); searchResultCache.put(String.format("%02d", bundleId), bundle); } } } else if (type.equals(SearchType.JDBCDRIVER)) { // Getting resource driver if (capability.getName().equals(CAPABILITY_JDBCDRIVER_NAME)) { // Getting all resource properties Map<String, Object> capabilityProperties = capability.getPropertiesAsMap(); boolean match = false; for (Entry capabilityProperty : capabilityProperties.entrySet()) { String capabilityKey = (String) capabilityProperty.getKey(); // Getting driver class if (capabilityKey.toLowerCase().equals("driver")) { String capabilityDriver = (String) capabilityProperty.getValue(); if (capabilityDriver.startsWith(searchTerms)) { match = true; } } } if (match) { bundleId++; bundlesToInstall.add(bundle); searchResultCache.put(String.format("%02d", bundleId), bundle); } } } else if (type.equals(SearchType.LIBRARY)) { // TODO: Implement library bundle search } } } } } /** * Method to check if some bundle is installed on OSGi * @param bundle * @return */ private boolean checkIfBundleIsInstalled(ObrBundle bundle) { // Refreshing installed bundles Bundle[] allInstalledBundles = context.getBundles(); // Checking if bundles is installed for (Bundle installedBundle : allInstalledBundles) { if (installedBundle.getSymbolicName().equals(bundle.getSymbolicName())) { return true; } } return false; } /** * Method to populate current Bundles on installed repositories */ private void populateBundleCache() { // Refreshing Repositories populateRepositories(); // Cleaning Bundle Cache bundleCache.clear(); for (Repository repo : repositories) { Resource[] repoResources = repo.getResources(); for (Resource repoResource : repoResources) { // Creating bundle of current resource ObrBundle bundle = new ObrBundle(repoResource.getSymbolicName(), repoResource.getPresentationName(), repoResource.getSize(), repoResource.getVersion(), repoResource.getURI()); // Getting Resource Capabilites Capability[] resourceCapabilities = repoResource.getCapabilities(); for (Capability capability : resourceCapabilities) { // Getting resource commands if (capability.getName().equals(CAPABILITY_COMMANDS_NAME) || capability.getName().equals(CAPABILITY_JDBCDRIVER_NAME) || capability.getName().equals(CAPABILITY_LIBRARY_NAME)) { // Getting all resource properties Map<String, Object> capabilityProperties = capability.getPropertiesAsMap(); for (Entry capabilityProperty : capabilityProperties.entrySet()) { String capabilityCommand = (String) capabilityProperty.getValue(); bundle.addCommand(capabilityCommand); } bundleCache.put(bundle.getSymbolicName(), bundle); } } } } } private void printResultList(List<ObrBundle> bundles) { final StringBuilder sb = new StringBuilder(); int bundleId = 1; int maxSymbolicNameLength = getSymbolicNameMaxLength(bundles); LOGGER.warning(String.format("ID BUNDLE SYMBOLIC NAME%s DESCRIPTION", printSpaces(maxSymbolicNameLength - "BUNDLE SYMBOLIC NAME".length()))); LOGGER .warning("--------------------------------------------------------------------------------"); for (final ObrBundle bundle : bundles) { final String bundleKey = String.format("%02d", bundleId++); sb.append(bundleKey); sb.append(" "); sb.append(bundle.getSymbolicName()); sb.append(String.format("%s ", printSpaces(maxSymbolicNameLength - bundle.getSymbolicName().length()))); sb.append(bundle.getPresentationName()); if (sb.toString().trim().length() > 0) { LOGGER.info(sb.toString()); } sb.setLength(0); } LOGGER .warning("--------------------------------------------------------------------------------"); LOGGER .info("[HINT] use 'addon info bundle --bundleSymbolicName' to see details about a search result"); LOGGER .info("[HINT] use 'addon install bundle --bundleSymbolicName' to install a specific add-on version"); } /** * Method to print Spaces by total Spaces */ public String printSpaces(int numberOfSpaces) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < numberOfSpaces; i++) { sb.append(" "); } return sb.toString(); } /** * Method to obtain max length of Symbolic Names */ public int getSymbolicNameMaxLength(List<ObrBundle> bundles) { int maxSymbolicNameLength = 0; for (final ObrBundle bundle : bundles) { String symbolicName = bundle.getSymbolicName(); if (symbolicName.length() > maxSymbolicNameLength) { maxSymbolicNameLength = symbolicName.length(); } } return maxSymbolicNameLength; } @Override public void addOnInfo(ObrAddOnBundleSymbolicName bsn) { Validate.notNull(bsn, "A valid add-on bundle symbolic name is required"); // Refreshing bundle cache populateBundleCache(); synchronized (mutex) { String bsnString = bsn.getKey(); if (bsnString.contains(";")) { bsnString = bsnString.split(";")[0]; } final ObrBundle bundle = bundleCache.get(bsnString); if (bundle == null) { LOGGER.warning("Unable to find specified bundle with symbolic name: " + bsn.getKey()); return; } addOnInfo(bundle, bundle.getVersion()); } } @Override public void addOnInfo(String bundleId) { Validate.notBlank(bundleId, "A valid bundle ID is required"); synchronized (mutex) { ObrBundle bundle = null; if (searchResultCache != null) { bundle = searchResultCache.get(String.format("%02d", Integer.parseInt(bundleId))); } if (bundle == null) { LOGGER.warning("A valid bundle ID is required"); return; } addOnInfo(bundle, bundle.getVersion()); } } @Override public Map<String, ObrBundle> getAddOnCache() { synchronized (mutex) { populateBundleCache(); return Collections.unmodifiableMap(bundleCache); } } @Override public InstallOrUpgradeStatus installAddOn(ObrAddOnBundleSymbolicName bsn) { // Refreshing bundle cache populateBundleCache(); synchronized (mutex) { Validate.notNull(bsn, "A valid add-on bundle symbolic name is required"); String bsnString = bsn.getKey(); if (bsnString.contains(";")) { bsnString = bsnString.split(";")[0]; } final ObrBundle bundle = bundleCache.get(bsnString); if (bundle == null) { LOGGER.warning("Could not find specified bundle with symbolic name: " + bsn.getKey()); return InstallOrUpgradeStatus.FAILED; } return installAddon(bundle, bsn.getKey()); } } @Override public InstallOrUpgradeStatus installAddOn(String bundleId) { synchronized (mutex) { Validate.notBlank(bundleId, "A valid bundle ID is required"); ObrBundle bundle = null; if (searchResultCache != null) { bundle = searchResultCache.get(String.format("%02d", Integer.parseInt(bundleId))); } if (bundle == null) { LOGGER.warning("To install an addon a valid bundle ID is required"); return InstallOrUpgradeStatus.FAILED; } return installAddon(bundle, bundle.getSymbolicName()); } } @Override public InstallOrUpgradeStatus installAddOnByUrl(String url) { synchronized (mutex) { Validate.notNull(url, "Valid url is required"); boolean success = getShell().executeCommand("!g felix:install " + url); success = getShell().executeCommand("!g felix:start " + url); return success ? InstallOrUpgradeStatus.SUCCESS : InstallOrUpgradeStatus.FAILED; } } @Override public InstallOrUpgradeStatus removeAddOn(BundleSymbolicName bsn) { synchronized (mutex) { InstallOrUpgradeStatus status; try { Validate.notNull(bsn, "Bundle symbolic name required"); bsn.findBundleWithoutFail(context).uninstall(); LOGGER.log(Level.INFO, String.format("Bundle '%s' : Uninstalled!", bsn.getKey())); LOGGER.log(Level.INFO, ""); status = InstallOrUpgradeStatus.SUCCESS; return status; } catch (BundleException e) { LOGGER.warning("Unable to remove add-on: " + bsn.getKey()); status = InstallOrUpgradeStatus.FAILED; return status; } } } /** * This method shows addon info on Spring Roo Shell * * @param bundle * @param bundleVersion */ private void addOnInfo(final ObrBundle bundle, final Version bundleVersion) { logInfo("Name", bundle.getPresentationName()); logInfo("BSN", bundle.getSymbolicName()); logInfo("Version", bundleVersion.toString()); logInfo("JAR Size", bundle.getSize() + " bytes"); logInfo("JAR URL", bundle.getUri()); StringBuilder sb = new StringBuilder(); for (final String command : bundle.getCommands()) { sb.append(command).append(", "); } logInfo("Commands", sb.toString()); } private void logInfo(final String label, String content) { final StringBuilder sb = new StringBuilder(); sb.append(label); for (int i = 0; i < 13 - label.length(); i++) { sb.append("."); } sb.append(": "); if (content.length() < 65) { sb.append(content); LOGGER.info(sb.toString()); } else { final List<String> split = new ArrayList<String>(Arrays.asList(content.split("\\s"))); if (split.size() == 1) { while (content.length() > 65) { sb.append(content.substring(0, 65)); content = content.substring(65); LOGGER.info(sb.toString()); sb.setLength(0); sb.append(" "); } if (content.length() > 0) { LOGGER.info(sb.append(content).toString()); } } else { while (split.size() > 0) { while (!split.isEmpty() && split.get(0).length() + sb.length() < 79) { sb.append(split.get(0)).append(" "); split.remove(0); } LOGGER.info(sb.toString().substring(0, sb.toString().length() - 1)); sb.setLength(0); sb.append(" "); } } } } /** * This method will install bundle on Spring Roo Repository * * @param bundleVersion * @param bsn * @return */ private InstallOrUpgradeStatus installAddon(final ObrBundle bundle, final String bsn) { final InstallOrUpgradeStatus status = installOrUpgradeAddOn(bundle); switch (status) { case SUCCESS: LOGGER.info("Successfully installed add-on: " + bundle.getPresentationName() + " [version: " + bundle.getVersion() + "]"); break; default: LOGGER.warning("Unable to install add-on: " + bundle.getPresentationName() + " [version: " + bundle.getVersion() + "]"); break; } return status; } /** * * This method will install Addon * * @param bundle * @param bsn * @param install * @return */ private InstallOrUpgradeStatus installOrUpgradeAddOn(final ObrBundle bundle) { boolean success = getShell().executeCommand("!g obr:deploy " + bundle.getSymbolicName()); success = getShell().executeCommand("!g obr:start " + bundle.getSymbolicName()); return success ? InstallOrUpgradeStatus.SUCCESS : InstallOrUpgradeStatus.FAILED; } @Override public void list() { synchronized (mutex) { getShell().executeCommand("!g lb"); } } /** * Method to get RepositoryAdmin Service implementation * * @return */ public RepositoryAdmin getRepositoryAdmin() { if (repositoryAdmin == null) { // Get all Services implement RepositoryAdmin interface try { ServiceReference<?>[] references = context.getAllServiceReferences(RepositoryAdmin.class.getName(), null); for (ServiceReference<?> ref : references) { repositoryAdmin = (RepositoryAdmin) context.getService(ref); return repositoryAdmin; } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load RepositoryAdmin on AddonSearchImpl."); return null; } } else { return repositoryAdmin; } } /** * Method to get Shell Service implementation * * @return */ public Shell getShell() { if (shell == null) { // Get all Services implement Shell interface try { ServiceReference<?>[] references = context.getAllServiceReferences(Shell.class.getName(), null); for (ServiceReference<?> ref : references) { shell = (Shell) context.getService(ref); return shell; } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load Shell on AddonSearchImpl."); return null; } } else { return shell; } } }