This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/SLING-8291_expose-error in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-provider-installhook.git
commit 9a95ab7d4f7325e64f792c5cea4bf6445d4f0c43 Author: georg.henzler <[email protected]> AuthorDate: Thu Oct 4 23:59:10 2018 +0200 SLING-7790 use of InfoProvider, additional package properties for configuration --- .../provider/installhook/OsgiInstallerHook.java | 784 ++++++++++++--------- .../installhook/OsgiInstallerListener.java | 91 +-- .../installhook/OsgiInstallerHookTest.java | 159 +++-- .../installhook/OsgiInstallerListenerTest.java | 87 ++- 4 files changed, 625 insertions(+), 496 deletions(-) diff --git a/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHook.java b/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHook.java index a8d910b..fd27716 100644 --- a/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHook.java +++ b/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHook.java @@ -50,357 +50,467 @@ import org.apache.jackrabbit.vault.packaging.VaultPackage; import org.apache.sling.installer.api.InstallableResource; import org.apache.sling.installer.api.OsgiInstaller; import org.apache.sling.installer.api.event.InstallationListener; +import org.apache.sling.installer.api.info.InfoProvider; +import org.apache.sling.installer.api.info.InstallationState; +import org.apache.sling.installer.api.info.Resource; +import org.apache.sling.installer.api.info.ResourceGroup; +import org.apache.sling.installer.api.tasks.ResourceState; import org.apache.sling.settings.SlingSettingsService; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; -import org.osgi.service.cm.Configuration; -import org.osgi.service.cm.ConfigurationAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class OsgiInstallerHook implements InstallHook { - private static final Logger LOG = LoggerFactory.getLogger(OsgiInstallerHook.class); + private static final Logger LOG = LoggerFactory.getLogger(OsgiInstallerHook.class); - private static final String PACKAGE_PROPERTY_MAX_WAIT_IN_SEC = "maxWaitForOsgiInstallerInSec"; - private static final String PACKAGE_PROP_INSTALL_PATH_REGEX = "installPathRegex"; + private static final String PACKAGE_PROP_INSTALL_PATH_REGEX = "installPathRegex"; + private static final String PACKAGE_PROPERTY_MAX_WAIT_IN_SEC = "maxWaitForOsgiInstallerInSec"; + private static final String PACKAGE_PROPERTY_INSTALL_PRIORITY = "osgiInstallerPriority"; + private static final String PACKAGE_PROPERTY_WAIT_FOR_OSGI_EVENTS_QUIET_IN_SEC = "waitForOsgiEventsQuietInSec"; - public static final int DEFAULT_PRIORITY_INSTALL_HOOK = 2000; - private static final int DEFAULT_MAX_WAIT_IN_SEC = 60; + public static final int DEFAULT_PRIORITY_INSTALL_HOOK = 2000; + private static final int DEFAULT_MAX_WAIT_IN_SEC = 60; + private static final int DEFAULT_WAIT_FOR_OSGI_EVENTS_QUIET_IN_SEC = 1; - public static final String URL_SCHEME = "jcrinstall"; - public static final String CONFIG_SUFFIX = ".config"; - public static final String JAR_SUFFIX = ".jar"; + public static final String URL_SCHEME = "jcrinstall"; + public static final String CONFIG_SUFFIX = ".config"; + public static final String JAR_SUFFIX = ".jar"; + + private static final String ENTITY_ID_PREFIX_BUNDLE = "bundle:"; + + private static final String MANIFEST_BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; + private static final String MANIFEST_BUNDLE_VERSION = "Bundle-Version"; + private static final String FOLDER_META_INF = "META-INF"; + + static final String JCR_CONTENT = "jcr:content"; + static final String JCR_CONTENT_DATA = JCR_CONTENT + "/jcr:data"; + static final String JCR_LAST_MODIFIED = "jcr:lastModified"; + static final String JCR_CONTENT_LAST_MODIFIED = JCR_CONTENT + "/" + JCR_LAST_MODIFIED; + + public static final String DOT = "."; + + InstallHookLogger logger = new InstallHookLogger(); + + @Override + public void execute(InstallContext context) throws PackageException { + + VaultPackage vaultPackage = context.getPackage(); + PackageProperties packageProperties = vaultPackage.getProperties(); + String installPathRegex = packageProperties.getProperty(PACKAGE_PROP_INSTALL_PATH_REGEX); + + ServiceReference<OsgiInstaller> osgiInstallerServiceRef = null; + ServiceReference<SlingSettingsService> slingSettingsServiceRef = null; + ServiceRegistration<InstallationListener> hookInstallationListenerServiceRegistration = null; + + ServiceReference<InfoProvider> infoProviderServiceRef = null; + + try { + switch (context.getPhase()) { + case PREPARE: + if (StringUtils.isBlank(installPathRegex)) { + throw new IllegalArgumentException( + "When using OSGi installer install hook for synchronous installation, the package property " + + PACKAGE_PROP_INSTALL_PATH_REGEX + " has to be provided."); + } + break; + case INSTALLED: + ImportOptions options = context.getOptions(); + logger.setOptions(options); + + logger.log(getClass().getSimpleName() + " is active in " + vaultPackage.getId()); + + List<BundleInPackage> bundleResources = new ArrayList<>(); + List<String> configResourcePaths = new ArrayList<>(); + Archive archive = vaultPackage.getArchive(); + + infoProviderServiceRef = getBundleContext().getServiceReference(InfoProvider.class); + InfoProvider infoProvider = (InfoProvider) getBundleContext().getService(infoProviderServiceRef); + InstallationState installationState = infoProvider.getInstallationState(); + + slingSettingsServiceRef = getBundleContext().getServiceReference(SlingSettingsService.class); + SlingSettingsService slingSettingsService = (SlingSettingsService) getBundleContext().getService(slingSettingsServiceRef); + Set<String> runModes = slingSettingsService.getRunModes(); + + collectResources(archive, archive.getRoot(), "", bundleResources, configResourcePaths, installPathRegex, + runModes); + + logger.log("Bundles in package " + bundleResources); + + Session session = context.getSession(); + + Map<String, InstallableResource> bundlesToInstallByUrl = getBundlesToInstall(bundleResources, session, installationState, + packageProperties); + Map<String, InstallableResource> configsToInstallByUrl = getConfigsToInstall(configResourcePaths, session, + installationState, packageProperties); + + if (bundlesToInstallByUrl.isEmpty() && configsToInstallByUrl.isEmpty()) { + logger.log("No installable resources that are not installed yet found."); + return; + } + + logger.log("Installing " + bundlesToInstallByUrl.size() + " bundles and " + + configsToInstallByUrl.size() + " configs"); + osgiInstallerServiceRef = getBundleContext().getServiceReference(OsgiInstaller.class); + OsgiInstaller osgiInstaller = getBundleContext().getService(osgiInstallerServiceRef); + + OsgiInstallerListener hookInstallationListener = new OsgiInstallerListener(bundlesToInstallByUrl.keySet(), + configsToInstallByUrl.keySet()); + hookInstallationListenerServiceRegistration = getBundleContext() + .registerService(InstallationListener.class, hookInstallationListener, null); + + List<InstallableResource> resourcesToUpdate = new ArrayList<>(); + resourcesToUpdate.addAll(bundlesToInstallByUrl.values()); + resourcesToUpdate.addAll(configsToInstallByUrl.values()); + logger.log("Updating resources " + resourcesToUpdate); + osgiInstaller.updateResources(URL_SCHEME, resourcesToUpdate.toArray(new InstallableResource[resourcesToUpdate.size()]), + null); + + int maxWaitForOsgiInstallerInSec = getNumericPackageProperty(packageProperties, PACKAGE_PROPERTY_MAX_WAIT_IN_SEC, + DEFAULT_MAX_WAIT_IN_SEC); + + long startTime = System.currentTimeMillis(); + int bundlesLeftToInstall = 0; + int configsLeftToInstall = 0; + while ((bundlesLeftToInstall = hookInstallationListener.bundlesLeftToInstall()) > 0 + || (configsLeftToInstall = hookInstallationListener.configsLeftToInstall()) > 0) { + if ((System.currentTimeMillis() - startTime) > maxWaitForOsgiInstallerInSec * 1000) { + logger.log("Installable resources " + resourcesToUpdate + + " could not be installed even after waiting " + maxWaitForOsgiInstallerInSec + "sec"); + break; + } + logger.log("Waiting for " + bundlesLeftToInstall + " bundles / " + configsLeftToInstall + " configs to be installed"); + Thread.sleep(1000); + } + if (bundlesLeftToInstall == 0 && configsLeftToInstall == 0) { + logger.log("All " + bundlesToInstallByUrl.size() + " bundles / " + configsToInstallByUrl.size() + + " configs have been successfully installed"); + } + + int waitForOsgiEventsQuietInSec = getNumericPackageProperty(packageProperties, + PACKAGE_PROPERTY_WAIT_FOR_OSGI_EVENTS_QUIET_IN_SEC, DEFAULT_WAIT_FOR_OSGI_EVENTS_QUIET_IN_SEC); + waitForServiceChanges(waitForOsgiEventsQuietInSec); + + break; + default: + break; + } + } catch (Exception e) { + throw new PackageException("Could not execute install hook to for synchronous installation: " + e, e); + } finally { + if (osgiInstallerServiceRef != null) { + getBundleContext().ungetService(osgiInstallerServiceRef); + } + if (slingSettingsServiceRef != null) { + getBundleContext().ungetService(slingSettingsServiceRef); + } + if (infoProviderServiceRef != null) { + getBundleContext().ungetService(infoProviderServiceRef); + } + + if (hookInstallationListenerServiceRegistration != null) { + hookInstallationListenerServiceRegistration.unregister(); + } + } + } + + private int getNumericPackageProperty(PackageProperties packageProperties, String propertyName, int defaultVal) { + String strVal = packageProperties.getProperty(propertyName); + int intVal = strVal != null ? Integer.parseInt(strVal) : defaultVal; + return intVal; + } + + private Map<String, InstallableResource> getBundlesToInstall(List<BundleInPackage> bundlesInPackage, Session session, + InstallationState installationState, PackageProperties packageProperties) throws RepositoryException, IOException { + Map<String, InstallableResource> installableResources = new HashMap<>(); + Iterator<BundleInPackage> bundlesIt = bundlesInPackage.iterator(); + while (bundlesIt.hasNext()) { + BundleInPackage bundle = bundlesIt.next(); + + List<Resource> currentInstallerBundleResources = getBundleResources(installationState, bundle.symbolicName); + + boolean needsInstallation = false; + if (currentInstallerBundleResources.isEmpty()) { + needsInstallation = true; + } else if (currentInstallerBundleResources.size() == 1) { + Resource resource = currentInstallerBundleResources.get(0); + + if (resource.getState() == ResourceState.INSTALLED) { + String currentlyActiveBundleVersion = resource.getVersion().toString(); + if (!StringUtils.equals(currentlyActiveBundleVersion, bundle.version)) { + logger.log("Bundle " + bundle.symbolicName + " is installed with version " + + currentlyActiveBundleVersion + " but package contains version " + bundle.version); + needsInstallation = true; + } else { + logger.log("Bundle " + bundle.symbolicName + " is already installed with version " + + currentlyActiveBundleVersion + " that matches " + bundle.version + " as provided in package"); + } + } else { + logger.log("Bundle " + bundle.symbolicName + " is not in state INSTALLED but in " + resource.getState()); + needsInstallation = true; + } + + } else { + logger.log("Bundle " + bundle.symbolicName + " exists with multiple installer resources"); + boolean installedBundleResourceFound = false; + for (Resource resource : currentInstallerBundleResources) { + logger.log("Resource " + resource); + if (resource.getState() == ResourceState.INSTALLED + && StringUtils.equals(resource.getVersion().toString(), bundle.version)) { + installedBundleResourceFound = true; + } + } + if (!installedBundleResourceFound) { + needsInstallation = true; + } + + } + + if (needsInstallation) { + logger.log("Bundle " + bundle.symbolicName + " requires installation"); + Node node = session.getNode(bundle.path); + InstallableResource installableResource = convert(node, bundle.path, packageProperties); + String bundleUrl = URL_SCHEME + ":" + bundle.path; + installableResources.put(bundleUrl, installableResource); + } + } + return installableResources; + } + + private List<Resource> getBundleResources(InstallationState installationState, String symbolicId) { + + List<Resource> bundleResources = new ArrayList<Resource>(); + + List<ResourceGroup> allGroups = new ArrayList<ResourceGroup>(); + allGroups.addAll(installationState.getInstalledResources()); + allGroups.addAll(installationState.getActiveResources()); + for (ResourceGroup resourceGroup : allGroups) { + List<Resource> resources = resourceGroup.getResources(); + for (Resource resource : resources) { + if (StringUtils.equals(resource.getEntityId(), ENTITY_ID_PREFIX_BUNDLE + symbolicId)) { + bundleResources.add(resource); + } + } + } + return bundleResources; + } + + private Map<String, InstallableResource> getConfigsToInstall(List<String> configResourcePaths, Session session, + InstallationState installationState, PackageProperties packageProperties) + throws IOException, InvalidSyntaxException, RepositoryException { + Map<String, InstallableResource> configsToInstallByUrl = new HashMap<>(); + for (String configResourcePath : configResourcePaths) { + boolean needsInstallation = false; + + String configUrl = URL_SCHEME + ":" + configResourcePath; + boolean configFound = false; + List<ResourceGroup> installedResources = installationState.getInstalledResources(); + for (ResourceGroup resourceGroup : installedResources) { + for (Resource resource : resourceGroup.getResources()) { + if (StringUtils.equals(configUrl, resource.getURL())) { + configFound = true; + logger.log("Config " + configResourcePath + " is already installed"); + } + } + } + if (!configFound) { + logger.log("Config " + configResourcePath + " has not been installed"); + needsInstallation = true; + } + + if (needsInstallation) { + + Node node = session.getNode(configResourcePath); + InstallableResource installableResource = convert(node, configResourcePath, packageProperties); + + configsToInstallByUrl.put(configUrl, installableResource); + } + } + return configsToInstallByUrl; + } + + void collectResources(Archive archive, Entry entry, String dirPath, List<BundleInPackage> bundleResources, + List<String> configResources, String installPathRegex, Set<String> actualRunmodes) { + String entryName = entry.getName(); + if (entryName.equals(FOLDER_META_INF)) { + return; + } + + String dirPathWithoutJcrRoot = StringUtils.substringAfter(dirPath, "/jcr_root"); + String entryPath = dirPathWithoutJcrRoot + entryName; + String dirPathWithoutSlash = StringUtils.chomp(dirPathWithoutJcrRoot, "/"); + + boolean runmodesMatch; + if (dirPathWithoutSlash.contains(DOT)) { + String[] bits = dirPathWithoutSlash.split("\\" + DOT, 2); + List<String> runmodesOfResource = Arrays.asList(bits[1].split("\\" + DOT)); + Set<String> matchingRunmodes = new HashSet<String>(runmodesOfResource); + matchingRunmodes.retainAll(actualRunmodes); + LOG.debug("Entry with runmode(s): entryPath={} runmodesOfResource={} actualRunmodes={} matchingRunmodes={}", + entryPath, runmodesOfResource, actualRunmodes, matchingRunmodes); + runmodesMatch = matchingRunmodes.size() == runmodesOfResource.size(); + if (!runmodesMatch) { + logger.log("Skipping installation of " + entryPath + + " because the path is not matching all actual runmodes " + actualRunmodes); + } + } else { + runmodesMatch = true; + } + + if (entryPath.matches(installPathRegex) && runmodesMatch) { + + if (entryName.endsWith(CONFIG_SUFFIX)) { + configResources.add(entryPath); + } else if (entryName.endsWith(JAR_SUFFIX)) { + try (InputStream entryInputStream = archive.getInputSource(entry).getByteStream(); + JarInputStream jarInputStream = new JarInputStream(entryInputStream)) { + Manifest manifest = jarInputStream.getManifest(); + String symbolicName = manifest.getMainAttributes().getValue(MANIFEST_BUNDLE_SYMBOLIC_NAME); + String version = manifest.getMainAttributes().getValue(MANIFEST_BUNDLE_VERSION); + + bundleResources.add(new BundleInPackage(entryPath, symbolicName, version)); + } catch (Exception e) { + throw new IllegalStateException( + "Could not read symbolic name and version from manifest of bundle " + entryName); + } + } + + } + + for (Entry child : entry.getChildren()) { + collectResources(archive, child, dirPath + entryName + "/", bundleResources, configResources, + installPathRegex, actualRunmodes); + } + } + + InstallableResource convert(final Node node, final String path, PackageProperties packageProperties) + throws IOException, RepositoryException { + LOG.trace("Converting {} at path {}", node, path); + final String digest = String.valueOf(node.getProperty(JCR_CONTENT_LAST_MODIFIED).getDate().getTimeInMillis()); + final InputStream is = node.getProperty(JCR_CONTENT_DATA).getStream(); + final Dictionary<String, Object> dict = new Hashtable<String, Object>(); + dict.put(InstallableResource.INSTALLATION_HINT, node.getParent().getName()); + int priority = getNumericPackageProperty(packageProperties, PACKAGE_PROPERTY_INSTALL_PRIORITY, DEFAULT_PRIORITY_INSTALL_HOOK); + return new InstallableResource(path, is, dict, digest, null, priority); + } + + private void waitForServiceChanges(int waitForOsgiEventsQuietInSec) { + if (waitForOsgiEventsQuietInSec <= 0) { + return; + } + InstallerHookOsgiEventListener osgiListener = new InstallerHookOsgiEventListener(); + BundleContext bundleContext = getBundleContext(); + bundleContext.addServiceListener(osgiListener); + bundleContext.addBundleListener(osgiListener); + + long waitStart = System.currentTimeMillis(); + osgiListener.waitUntilQuiet(waitForOsgiEventsQuietInSec); + logger.log("Waited " + (System.currentTimeMillis() - waitStart) + "ms in total for OSGi events to become quiet (for at least " + + waitForOsgiEventsQuietInSec + "sec)"); + + bundleContext.removeServiceListener(osgiListener); + bundleContext.removeBundleListener(osgiListener); + + } + + // always get fresh bundle context to avoid "Dynamic class loader has already + // been deactivated" exceptions + private BundleContext getBundleContext() { + // use the vault bundle to hook into the OSGi world + Bundle currentBundle = FrameworkUtil.getBundle(InstallHook.class); + if (currentBundle == null) { + throw new IllegalStateException( + "The class " + InstallHook.class + " was not loaded through a bundle classloader"); + } + BundleContext bundleContext = currentBundle.getBundleContext(); + if (bundleContext == null) { + throw new IllegalStateException("Could not get bundle context for bundle " + currentBundle); + } + return bundleContext; + } + + class BundleInPackage { + final String path; + final String symbolicName; + final String version; + + public BundleInPackage(String path, String symbolicName, String version) { + super(); + this.path = path; + this.symbolicName = symbolicName; + this.version = version; + } + + @Override + public String toString() { + return "BundleInPackage [path=" + path + ", symbolicId=" + symbolicName + ", version=" + version + "]"; + } + + } + + static class InstallHookLogger { + + private ProgressTrackerListener listener; + + public void setOptions(ImportOptions options) { + this.listener = options.getListener(); + } + + public void logError(Logger logger, String message, Throwable throwable) { + if (listener != null) { + listener.onMessage(ProgressTrackerListener.Mode.TEXT, "ERROR: " + message, ""); + } + logger.error(message, throwable); + } + + public void log(String message) { + log(LOG, message); + } + + public void log(Logger logger, String message) { + if (listener != null) { + listener.onMessage(ProgressTrackerListener.Mode.TEXT, message, ""); + logger.debug(message); + } else { + logger.info(message); + } + } + } + + static class InstallerHookOsgiEventListener implements ServiceListener, BundleListener { + + private long lastEventTimestamp = System.currentTimeMillis(); + + @Override + public void serviceChanged(ServiceEvent event) { + lastEventTimestamp = System.currentTimeMillis(); + LOG.trace("Service changed event {}", event); + } + + @Override + public void bundleChanged(BundleEvent event) { + lastEventTimestamp = System.currentTimeMillis(); + LOG.trace("Bundle changed event {}", event); + } + + public void waitUntilQuiet(long waitInSec) { + try { + while (System.currentTimeMillis() - lastEventTimestamp < waitInSec * 1000) { + Thread.sleep(100); + } + } catch (InterruptedException e) { + LOG.warn("Wait for OSGi events was interrupted"); + } + } + } - private static final String MANIFEST_BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; - private static final String MANIFEST_BUNDLE_VERSION = "Bundle-Version"; - private static final String FOLDER_META_INF = "META-INF"; - - static final String JCR_CONTENT = "jcr:content"; - static final String JCR_CONTENT_DATA = JCR_CONTENT + "/jcr:data"; - static final String JCR_LAST_MODIFIED = "jcr:lastModified"; - static final String JCR_CONTENT_LAST_MODIFIED = JCR_CONTENT + "/" + JCR_LAST_MODIFIED; - - public static final String DOT = "."; - - InstallHookLogger logger = new InstallHookLogger(); - - @Override - public void execute(InstallContext context) throws PackageException { - - VaultPackage vaultPackage = context.getPackage(); - PackageProperties packageProperties = vaultPackage.getProperties(); - String installPathRegex = packageProperties.getProperty(PACKAGE_PROP_INSTALL_PATH_REGEX); - - ServiceReference<OsgiInstaller> osgiInstallerServiceRef = null; - ServiceReference<ConfigurationAdmin> configAdminServiceRef = null; - ServiceReference<SlingSettingsService> slingSettingsServiceRef = null; - ServiceRegistration<InstallationListener> hookInstallationListenerServiceRegistration = null; - - try { - switch (context.getPhase()) { - case PREPARE: - if (StringUtils.isBlank(installPathRegex)) { - throw new IllegalArgumentException( - "When using OSGi installer install hook for synchronous installation, the package property " - + PACKAGE_PROP_INSTALL_PATH_REGEX + " has to be provided."); - } - break; - case INSTALLED: - ImportOptions options = context.getOptions(); - logger.setOptions(options); - - logger.log(getClass().getSimpleName() + " is active in " + vaultPackage.getId()); - - List<BundleInPackage> bundleResources = new ArrayList<>(); - List<String> configResourcePaths = new ArrayList<>(); - Archive archive = vaultPackage.getArchive(); - - configAdminServiceRef = getBundleContext().getServiceReference(ConfigurationAdmin.class); - ConfigurationAdmin confAdmin = (ConfigurationAdmin) getBundleContext() - .getService(configAdminServiceRef); - - slingSettingsServiceRef = getBundleContext().getServiceReference(SlingSettingsService.class); - SlingSettingsService slingSettingsService = (SlingSettingsService) getBundleContext() - .getService(slingSettingsServiceRef); - Set<String> runModes = slingSettingsService.getRunModes(); - - collectResources(archive, archive.getRoot(), "", bundleResources, configResourcePaths, installPathRegex, - runModes); - - logger.log("Bundles in package " + bundleResources); - - Map<String, String> bundleVersionsBySymbolicId = new HashMap<>(); - for (Bundle bundle : getBundleContext().getBundles()) { - bundleVersionsBySymbolicId.put(bundle.getSymbolicName(), bundle.getVersion().toString()); - } - - Session session = context.getSession(); - - List<InstallableResource> installableResources = new ArrayList<>(); - - Set<String> bundleSymbolicNamesToInstall = getBundlesToInstall(bundleResources, - bundleVersionsBySymbolicId, session, installableResources); - - Set<String> configPidsToInstall = getConfigPidsToInstall(configResourcePaths, session, - installableResources, confAdmin); - - if (installableResources.isEmpty()) { - logger.log("No installable resources that are not installed yet found."); - return; - } - - logger.log("Installing " + bundleSymbolicNamesToInstall.size() + " bundles and " - + configPidsToInstall.size() + " configs"); - osgiInstallerServiceRef = getBundleContext().getServiceReference(OsgiInstaller.class); - OsgiInstaller osgiInstaller = getBundleContext().getService(osgiInstallerServiceRef); - - OsgiInstallerListener hookInstallationListener = new OsgiInstallerListener(bundleSymbolicNamesToInstall, - configPidsToInstall); - hookInstallationListenerServiceRegistration = getBundleContext() - .registerService(InstallationListener.class, hookInstallationListener, null); - - logger.log("Update resources " + installableResources); - osgiInstaller.updateResources(URL_SCHEME, - installableResources.toArray(new InstallableResource[installableResources.size()]), null); - - String maxWaitForOsgiInstallerInSecStr = packageProperties - .getProperty(PACKAGE_PROPERTY_MAX_WAIT_IN_SEC); - int maxWaitForOsgiInstallerInSec = maxWaitForOsgiInstallerInSecStr != null - ? Integer.parseInt(maxWaitForOsgiInstallerInSecStr) - : DEFAULT_MAX_WAIT_IN_SEC; - - long startTime = System.currentTimeMillis(); - while (!hookInstallationListener.isDone()) { - if ((System.currentTimeMillis() - startTime) > maxWaitForOsgiInstallerInSec * 1000) { - logger.log("Installable resources " + installableResources - + " could not be installed even after waiting " + maxWaitForOsgiInstallerInSec + "sec"); - break; - } - logger.log("Waiting for " + installableResources.size() + " to be installed"); - Thread.sleep(1000); - } - - break; - default: - break; - } - } catch (Exception e) { - throw new PackageException("Could not execute install hook to for synchronous installation: " + e, e); - } finally { - if (osgiInstallerServiceRef != null) { - getBundleContext().ungetService(osgiInstallerServiceRef); - } - if (configAdminServiceRef != null) { - getBundleContext().ungetService(configAdminServiceRef); - } - if (slingSettingsServiceRef != null) { - getBundleContext().ungetService(slingSettingsServiceRef); - } - - if (hookInstallationListenerServiceRegistration != null) { - hookInstallationListenerServiceRegistration.unregister(); - } - } - } - - private Set<String> getConfigPidsToInstall(List<String> configResourcePaths, Session session, - List<InstallableResource> installableResources, ConfigurationAdmin confAdmin) - throws IOException, InvalidSyntaxException, RepositoryException { - Set<String> configIdsToInstall = new HashSet<>(); - for (String configResourcePath : configResourcePaths) { - boolean needsInstallation = false; - String configIdToInstall = StringUtils - .substringBefore(StringUtils.substringAfterLast(configResourcePath, "/"), CONFIG_SUFFIX); - if (!configIdToInstall.contains("-")) { - // non-factory configs - Configuration[] activeConfigs = confAdmin.listConfigurations("(service.pid=" + configIdToInstall + ")"); - if (activeConfigs == null) { - logger.log("Config PID " + configIdToInstall + " requires installation"); - - needsInstallation = true; - } - } else { - // non-factory configs - String factoryPid = StringUtils.substringBefore(configIdToInstall, "-"); - Configuration[] activeConfigs = confAdmin.listConfigurations("(service.factoryPid=" + factoryPid + ")"); - if (activeConfigs == null) { - logger.log("There is not a single config for factory PID " + factoryPid + " in system, " - + configIdToInstall + " requires installation"); - needsInstallation = true; - } - } - - if (needsInstallation) { - Node node = session.getNode(configResourcePath); - InstallableResource installableResource = convert(node, configResourcePath); - installableResources.add(installableResource); - configIdsToInstall.add(configIdToInstall); - } - } - return configIdsToInstall; - } - - private Set<String> getBundlesToInstall(List<BundleInPackage> bundleResources, - Map<String, String> bundleVersionsBySymbolicId, Session session, - List<InstallableResource> installableResources) throws RepositoryException, IOException { - Set<String> bundleSymbolicNamesToInstall = new HashSet<>(); - Iterator<BundleInPackage> bundlesIt = bundleResources.iterator(); - while (bundlesIt.hasNext()) { - BundleInPackage bundle = bundlesIt.next(); - - String currentlyActiveBundleVersion = bundleVersionsBySymbolicId.get(bundle.symbolicName); - boolean needsInstallation = false; - if (currentlyActiveBundleVersion == null) { - logger.log("Bundle " + bundle.symbolicName + " is not installed"); - needsInstallation = true; - } else if (!currentlyActiveBundleVersion.equals(bundle.version)) { - logger.log("Bundle " + bundle.symbolicName + " is installed with version " - + currentlyActiveBundleVersion + " but package contains version " + bundle.version); - needsInstallation = true; - } else { - logger.log("Bundle " + bundle.symbolicName + " is already installed with version " - + currentlyActiveBundleVersion); - } - if (needsInstallation) { - logger.log("Bundle " + bundle.symbolicName + " requires installation"); - Node node = session.getNode(bundle.path); - InstallableResource installableResource = convert(node, bundle.path); - installableResources.add(installableResource); - bundleSymbolicNamesToInstall.add(bundle.symbolicName); - } - } - return bundleSymbolicNamesToInstall; - } - - void collectResources(Archive archive, Entry entry, String dirPath, List<BundleInPackage> bundleResources, - List<String> configResources, String installPathRegex, Set<String> actualRunmodes) { - String entryName = entry.getName(); - if (entryName.equals(FOLDER_META_INF)) { - return; - } - - String dirPathWithoutJcrRoot = StringUtils.substringAfter(dirPath, "/jcr_root"); - String entryPath = dirPathWithoutJcrRoot + entryName; - String dirPathWithoutSlash = StringUtils.chomp(dirPathWithoutJcrRoot, "/"); - - boolean runmodesMatch; - if (dirPathWithoutSlash.contains(DOT)) { - String[] bits = dirPathWithoutSlash.split("\\" + DOT, 2); - List<String> runmodesOfResource = Arrays.asList(bits[1].split("\\" + DOT)); - Set<String> matchingRunmodes = new HashSet<String>(runmodesOfResource); - matchingRunmodes.retainAll(actualRunmodes); - LOG.debug("Entry with runmode(s): entryPath={} runmodesOfResource={} actualRunmodes={} matchingRunmodes={}", - entryPath, runmodesOfResource, actualRunmodes, matchingRunmodes); - runmodesMatch = matchingRunmodes.size() == runmodesOfResource.size(); - if (!runmodesMatch) { - logger.log("Skipping installation of " + entryPath - + " because the path is not matching all actual runmodes " + actualRunmodes); - } - } else { - runmodesMatch = true; - } - - if (entryPath.matches(installPathRegex) && runmodesMatch) { - - if (entryName.endsWith(CONFIG_SUFFIX)) { - configResources.add(entryPath); - } else if (entryName.endsWith(JAR_SUFFIX)) { - try (InputStream entryInputStream = archive.getInputSource(entry).getByteStream(); - JarInputStream jarInputStream = new JarInputStream(entryInputStream)) { - Manifest manifest = jarInputStream.getManifest(); - String symbolicName = manifest.getMainAttributes().getValue(MANIFEST_BUNDLE_SYMBOLIC_NAME); - String version = manifest.getMainAttributes().getValue(MANIFEST_BUNDLE_VERSION); - - bundleResources.add(new BundleInPackage(entryPath, symbolicName, version)); - } catch (Exception e) { - throw new IllegalStateException( - "Could not read symbolic name and version from manifest of bundle " + entryName); - } - } - - } - - for (Entry child : entry.getChildren()) { - collectResources(archive, child, dirPath + entryName + "/", bundleResources, configResources, - installPathRegex, actualRunmodes); - } - } - - InstallableResource convert(final Node node, final String path) throws IOException, RepositoryException { - logger.log("Converting " + node + " at path " + path); - final String digest = String.valueOf(node.getProperty(JCR_CONTENT_LAST_MODIFIED).getDate().getTimeInMillis()); - final InputStream is = node.getProperty(JCR_CONTENT_DATA).getStream(); - final Dictionary<String, Object> dict = new Hashtable<String, Object>(); - dict.put(InstallableResource.INSTALLATION_HINT, node.getParent().getName()); - return new InstallableResource(path, is, dict, digest, null, DEFAULT_PRIORITY_INSTALL_HOOK); - } - - // always get fresh bundle context to avoid "Dynamic class loader has already - // been deactivated" exceptions - private BundleContext getBundleContext() { - // use the vault bundle to hook into the OSGi world - Bundle currentBundle = FrameworkUtil.getBundle(InstallHook.class); - if (currentBundle == null) { - throw new IllegalStateException( - "The class " + InstallHook.class + " was not loaded through a bundle classloader"); - } - BundleContext bundleContext = currentBundle.getBundleContext(); - if (bundleContext == null) { - throw new IllegalStateException("Could not get bundle context for bundle " + currentBundle); - } - return bundleContext; - } - - class BundleInPackage { - final String path; - final String symbolicName; - final String version; - - public BundleInPackage(String path, String symbolicName, String version) { - super(); - this.path = path; - this.symbolicName = symbolicName; - this.version = version; - } - - @Override - public String toString() { - return "BundleInPackage [path=" + path + ", symbolicId=" + symbolicName + ", version=" + version + "]"; - } - - } - - static class InstallHookLogger { - - private ProgressTrackerListener listener; - - public void setOptions(ImportOptions options) { - this.listener = options.getListener(); - } - - public void logError(Logger logger, String message, Throwable throwable) { - if (listener != null) { - listener.onMessage(ProgressTrackerListener.Mode.TEXT, "ERROR: " + message, ""); - } - logger.error(message, throwable); - } - - public void log(String message) { - log(LOG, message); - } - - public void log(Logger logger, String message) { - if (listener != null) { - listener.onMessage(ProgressTrackerListener.Mode.TEXT, message, ""); - logger.debug(message); - } else { - logger.info(message); - } - } - } } diff --git a/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListener.java b/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListener.java index c0b1ace..4dd6b5a 100644 --- a/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListener.java +++ b/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListener.java @@ -18,10 +18,10 @@ */ package org.apache.sling.installer.provider.installhook; +import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.apache.commons.lang.StringUtils; import org.apache.sling.installer.api.event.InstallationEvent; import org.apache.sling.installer.api.event.InstallationEvent.TYPE; import org.apache.sling.installer.api.event.InstallationListener; @@ -31,57 +31,60 @@ import org.slf4j.LoggerFactory; public class OsgiInstallerListener implements InstallationListener { - private static final Logger LOG = LoggerFactory.getLogger(OsgiInstallerListener.class); + private static final Logger LOG = LoggerFactory.getLogger(OsgiInstallerListener.class); - static final String ENTITY_ID_PREFIX_BUNDLE = "bundle:"; - static final String ENTITY_ID_PREFIX_CONFIG = "config:"; + private final Set<String> initialBundleUrlsToInstall; + private final Set<String> initialConfigUrlsToInstall; - private final Set<String> requiredBundleSymbolicNames; - private final Set<String> requiredConfigPids; - private final Set<String> installedBundleSymbolicNames = new HashSet<>(); - private final Set<String> installedConfigPids = new HashSet<>(); + private final Set<String> bundleUrlsToInstall; + private final Set<String> configUrlsToInstall; - public OsgiInstallerListener(Set<String> requiredBundleSymbolicNames, Set<String> requiredConfigPids) { - this.requiredBundleSymbolicNames = requiredBundleSymbolicNames; - this.requiredConfigPids = requiredConfigPids; - } + public OsgiInstallerListener(Set<String> bundleUrlsToInstall, Set<String> configUrlsToInstall) { + this.initialBundleUrlsToInstall = bundleUrlsToInstall; + this.initialConfigUrlsToInstall = configUrlsToInstall; - @Override - public void onEvent(InstallationEvent installationEvent) { - if (installationEvent.getType() == TYPE.PROCESSED) { - Object sourceRaw = installationEvent.getSource(); - if (!(sourceRaw instanceof TaskResource)) { - throw new IllegalStateException("Expected source of type " + TaskResource.class.getName()); - } - TaskResource source = (TaskResource) sourceRaw; - String entityId = source.getEntityId(); + this.bundleUrlsToInstall = Collections.synchronizedSet(new HashSet<>(initialBundleUrlsToInstall)); + this.configUrlsToInstall = Collections.synchronizedSet(new HashSet<>(initialConfigUrlsToInstall)); + } - LOG.debug("Received event about processed entityId {}", entityId); + @Override + public void onEvent(InstallationEvent installationEvent) { + if (installationEvent.getType() == TYPE.PROCESSED) { + Object sourceRaw = installationEvent.getSource(); + if (!(sourceRaw instanceof TaskResource)) { + throw new IllegalStateException("Expected source of type " + TaskResource.class.getName()); + } + TaskResource source = (TaskResource) sourceRaw; + String entityId = source.getEntityId(); + String url = source.getURL(); - if (entityId.startsWith(ENTITY_ID_PREFIX_BUNDLE)) { - String installedBundleSymbolicName = StringUtils.substringAfter(entityId, ENTITY_ID_PREFIX_BUNDLE); - installedBundleSymbolicNames.add(installedBundleSymbolicName); - } else if (entityId.startsWith(ENTITY_ID_PREFIX_CONFIG)) { - String installedConfigPid = StringUtils.substringAfter(entityId, ENTITY_ID_PREFIX_CONFIG); - installedConfigPids.add(installedConfigPid); - } - } - } + LOG.trace("Received event about processed entityId={} url={}", entityId, url); - public boolean isDone() { - LOG.trace("requiredBundleSymbolicNames: {}", requiredBundleSymbolicNames); - LOG.trace("installedBundleSymbolicNames: {}", installedBundleSymbolicNames); - HashSet<String> bundlesLeftToInstall = new HashSet<String>(requiredBundleSymbolicNames); - bundlesLeftToInstall.removeAll(installedBundleSymbolicNames); - LOG.debug("bundlesLeftToInstall: {}", bundlesLeftToInstall); + if (bundleUrlsToInstall.contains(url)) { + LOG.debug("Received event for bundle installed with url={}", url); + bundleUrlsToInstall.remove(url); + } + if (configUrlsToInstall.contains(url)) { + LOG.debug("Received event for config installed with url={}", url); + configUrlsToInstall.remove(url); + } + } + } - LOG.trace("requiredConfigPids: {}", requiredConfigPids); - LOG.trace("installedConfigPids: {}", installedConfigPids); - HashSet<String> configsLeftToInstall = new HashSet<String>(requiredConfigPids); - configsLeftToInstall.removeAll(installedConfigPids); - LOG.debug("configsLeftToInstall: {}", configsLeftToInstall); + public int bundlesLeftToInstall() { + if (LOG.isTraceEnabled()) { + LOG.trace("initialBundleUrlsToInstall: {}", initialBundleUrlsToInstall); + LOG.trace("bundleUrlsToInstall: {}", bundleUrlsToInstall); + } + return bundleUrlsToInstall.size(); + } - return bundlesLeftToInstall.isEmpty() && configsLeftToInstall.isEmpty(); - } + public int configsLeftToInstall() { + if (LOG.isTraceEnabled()) { + LOG.trace("initialConfigUrlsToInstall: {}", initialConfigUrlsToInstall); + LOG.trace("configUrlsToInstall: {}", configUrlsToInstall); + } + return configUrlsToInstall.size(); + } } diff --git a/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHookTest.java b/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHookTest.java index 7d347d1..156fbfe 100644 --- a/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHookTest.java +++ b/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHookTest.java @@ -36,6 +36,7 @@ import javax.jcr.RepositoryException; import org.apache.jackrabbit.vault.fs.io.Archive; import org.apache.jackrabbit.vault.fs.io.Archive.Entry; +import org.apache.jackrabbit.vault.packaging.PackageProperties; import org.apache.sling.installer.api.InstallableResource; import org.apache.sling.installer.provider.installhook.OsgiInstallerHook.BundleInPackage; import org.junit.Before; @@ -47,83 +48,85 @@ import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class OsgiInstallerHookTest { - OsgiInstallerHook osgiInstallerHook; - - @Mock - Node node; - - @Mock - Node parentNode; - - @Mock - Property lastModifiedProperty; - - @Mock - Property contentDataProperty; - - @Mock - Archive archive; - - @Mock - Entry entry; - - Calendar now; - - @Before - public void setup() throws RepositoryException { - osgiInstallerHook = new OsgiInstallerHook(); - - now = Calendar.getInstance(); - when(node.getProperty(OsgiInstallerHook.JCR_CONTENT_LAST_MODIFIED)).thenReturn(lastModifiedProperty); - when(lastModifiedProperty.getDate()).thenReturn(now); - when(node.getProperty(OsgiInstallerHook.JCR_CONTENT_DATA)).thenReturn(contentDataProperty); - when(node.getParent()).thenReturn(parentNode); - - } - - @Test - public void testConvert() throws IOException, RepositoryException { - - File pathToTest = new File("/apps/myproj/install/mybundle.jar"); - when(parentNode.getName()).thenReturn(pathToTest.getParentFile().getName()); - - InstallableResource installableResource = osgiInstallerHook.convert(node, pathToTest.getAbsolutePath()); - - assertEquals(String.valueOf(now.getTimeInMillis()), installableResource.getDigest()); - assertEquals(pathToTest.getParentFile().getName(), installableResource.getDictionary().get(InstallableResource.INSTALLATION_HINT)); - - } - - public void testCollectResources() throws IOException, RepositoryException { - - File pathToTest = new File("/apps/myproj/install.author/myconfig.config"); - String dirPath = "/jcr_root" + pathToTest.getParentFile().getPath() + "/"; - - when(entry.getName()).thenReturn(pathToTest.getName()); - - List<BundleInPackage> bundleResources = new ArrayList<BundleInPackage>(); - List<String> configResources = new ArrayList<String>(); - - osgiInstallerHook.collectResources(archive, entry, dirPath, bundleResources, configResources, - "/apps/other.*", new HashSet<String>(Arrays.asList("author", "dev"))); - - assertTrue(bundleResources.isEmpty()); - assertTrue(configResources.isEmpty()); - - osgiInstallerHook.collectResources(archive, entry, dirPath, bundleResources, configResources, - "/apps/myproj.*", new HashSet<String>(Arrays.asList("publish", "dev"))); - - assertTrue(bundleResources.isEmpty()); - assertTrue(configResources.isEmpty()); - - - osgiInstallerHook.collectResources(archive, entry, dirPath, bundleResources, configResources, - "/apps/myproj.*", new HashSet<String>(Arrays.asList("author", "dev"))); - - assertTrue(bundleResources.isEmpty()); - assertEquals(1, configResources.size()); - assertEquals(pathToTest.getAbsolutePath(), configResources.get(0)); - - } + OsgiInstallerHook osgiInstallerHook; + + @Mock + Node node; + + @Mock + Node parentNode; + + @Mock + Property lastModifiedProperty; + + @Mock + Property contentDataProperty; + + @Mock + Archive archive; + + @Mock + PackageProperties packageProperties; + + @Mock + Entry entry; + + Calendar now; + + @Before + public void setup() throws RepositoryException { + osgiInstallerHook = new OsgiInstallerHook(); + + now = Calendar.getInstance(); + when(node.getProperty(OsgiInstallerHook.JCR_CONTENT_LAST_MODIFIED)).thenReturn(lastModifiedProperty); + when(lastModifiedProperty.getDate()).thenReturn(now); + when(node.getProperty(OsgiInstallerHook.JCR_CONTENT_DATA)).thenReturn(contentDataProperty); + when(node.getParent()).thenReturn(parentNode); + + } + + @Test + public void testConvert() throws IOException, RepositoryException { + + File pathToTest = new File("/apps/myproj/install/mybundle.jar"); + when(parentNode.getName()).thenReturn(pathToTest.getParentFile().getName()); + + InstallableResource installableResource = osgiInstallerHook.convert(node, pathToTest.getAbsolutePath(), packageProperties); + + assertEquals(String.valueOf(now.getTimeInMillis()), installableResource.getDigest()); + assertEquals(pathToTest.getParentFile().getName(), installableResource.getDictionary().get(InstallableResource.INSTALLATION_HINT)); + + } + + public void testCollectResources() throws IOException, RepositoryException { + + File pathToTest = new File("/apps/myproj/install.author/myconfig.config"); + String dirPath = "/jcr_root" + pathToTest.getParentFile().getPath() + "/"; + + when(entry.getName()).thenReturn(pathToTest.getName()); + + List<BundleInPackage> bundleResources = new ArrayList<BundleInPackage>(); + List<String> configResources = new ArrayList<String>(); + + osgiInstallerHook.collectResources(archive, entry, dirPath, bundleResources, configResources, + "/apps/other.*", new HashSet<String>(Arrays.asList("author", "dev"))); + + assertTrue(bundleResources.isEmpty()); + assertTrue(configResources.isEmpty()); + + osgiInstallerHook.collectResources(archive, entry, dirPath, bundleResources, configResources, + "/apps/myproj.*", new HashSet<String>(Arrays.asList("publish", "dev"))); + + assertTrue(bundleResources.isEmpty()); + assertTrue(configResources.isEmpty()); + + osgiInstallerHook.collectResources(archive, entry, dirPath, bundleResources, configResources, + "/apps/myproj.*", new HashSet<String>(Arrays.asList("author", "dev"))); + + assertTrue(bundleResources.isEmpty()); + assertEquals(1, configResources.size()); + assertEquals(pathToTest.getAbsolutePath(), configResources.get(0)); + + } } diff --git a/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListenerTest.java b/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListenerTest.java index 6cd02ea..90c755b 100644 --- a/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListenerTest.java +++ b/src/test/java/org/apache/sling/installer/provider/installhook/OsgiInstallerListenerTest.java @@ -18,12 +18,10 @@ */ package org.apache.sling.installer.provider.installhook; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -34,39 +32,54 @@ import org.junit.Test; public class OsgiInstallerListenerTest { - private OsgiInstallerListener osgiInstallerListener; - - @Test - public void testOsgiInstallerListener() { - - String bundleSymbolicId1 = "org.prj.bundle1"; - String bundleSymbolicId2 = "org.prj.bundle2"; - Set<String> requiredBundleSymbolicNames = new HashSet<String>(Arrays.asList(bundleSymbolicId1, bundleSymbolicId2)); - String configPid1 = "org.prj.config1"; - String configPid2 = "org.prj.config2"; - Set<String> requiredConfigPids = new HashSet<String>(Arrays.asList(configPid1, configPid2)); - - osgiInstallerListener = new OsgiInstallerListener(requiredBundleSymbolicNames, requiredConfigPids); - - assertFalse(osgiInstallerListener.isDone()); - osgiInstallerListener.onEvent(getInstallationEventMock(OsgiInstallerListener.ENTITY_ID_PREFIX_BUNDLE + bundleSymbolicId1)); - assertFalse(osgiInstallerListener.isDone()); - osgiInstallerListener.onEvent(getInstallationEventMock(OsgiInstallerListener.ENTITY_ID_PREFIX_BUNDLE + bundleSymbolicId2)); - assertFalse(osgiInstallerListener.isDone()); - osgiInstallerListener.onEvent(getInstallationEventMock(OsgiInstallerListener.ENTITY_ID_PREFIX_CONFIG + configPid1)); - assertFalse(osgiInstallerListener.isDone()); - osgiInstallerListener.onEvent(getInstallationEventMock(OsgiInstallerListener.ENTITY_ID_PREFIX_CONFIG + configPid2)); - assertTrue(osgiInstallerListener.isDone()); - - } - - public InstallationEvent getInstallationEventMock(String entityId) { - InstallationEvent event = mock(InstallationEvent.class); - when(event.getType()).thenReturn(TYPE.PROCESSED); - TaskResource taskResource = mock(TaskResource.class); - when(event.getSource()).thenReturn(taskResource); - when(taskResource.getEntityId()).thenReturn(entityId); - return event; - } + private OsgiInstallerListener osgiInstallerListener; + + @Test + public void testOsgiInstallerListener() { + + Set<String> bundleUrlsToInstall = new HashSet<String>(); + String bundleUrl1 = "jcrinstall:/apps/myproj/install/mybundle1.jar"; + bundleUrlsToInstall.add(bundleUrl1); + String bundleUrl2 = "jcrinstall:/apps/myproj/install/mybundle2.jar"; + bundleUrlsToInstall.add(bundleUrl2); + + Set<String> configUrlsToInstall = new HashSet<String>(); + String configUrl1 = "jcrinstall:/apps/myproj/config/conf1.config"; + configUrlsToInstall.add(configUrl1); + String configUrl2 = "jcrinstall:/apps/myproj/config/conf2.config"; + configUrlsToInstall.add(configUrl2); + + osgiInstallerListener = new OsgiInstallerListener(bundleUrlsToInstall, configUrlsToInstall); + + assertEquals(2, osgiInstallerListener.bundlesLeftToInstall()); + assertEquals(2, osgiInstallerListener.configsLeftToInstall()); + + osgiInstallerListener.onEvent(getInstallationEventMock(bundleUrl1)); + assertEquals(1, osgiInstallerListener.bundlesLeftToInstall()); + assertEquals(2, osgiInstallerListener.configsLeftToInstall()); + + osgiInstallerListener.onEvent(getInstallationEventMock(bundleUrl2)); + assertEquals(0, osgiInstallerListener.bundlesLeftToInstall()); + assertEquals(2, osgiInstallerListener.configsLeftToInstall()); + + osgiInstallerListener.onEvent(getInstallationEventMock(configUrl1)); + assertEquals(0, osgiInstallerListener.bundlesLeftToInstall()); + assertEquals(1, osgiInstallerListener.configsLeftToInstall()); + + osgiInstallerListener.onEvent(getInstallationEventMock(configUrl2)); + assertEquals(0, osgiInstallerListener.bundlesLeftToInstall()); + assertEquals(0, osgiInstallerListener.configsLeftToInstall()); + + } + + public InstallationEvent getInstallationEventMock(String url) { + InstallationEvent event = mock(InstallationEvent.class); + when(event.getType()).thenReturn(TYPE.PROCESSED); + TaskResource taskResource = mock(TaskResource.class); + when(event.getSource()).thenReturn(taskResource); + when(taskResource.getURL()).thenReturn(url); + when(taskResource.getEntityId()).thenReturn("dummyEntityId"); + return event; + } }
