You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2019/02/25 12:38:20 UTC
[sling-org-apache-sling-installer-provider-installhook] 01/24:
SLING-7790 Initial implementation
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 f813390c44954337b5acf5e9a80fc61d883cb22f
Author: Georg Henzler <ge...@netcentric.biz>
AuthorDate: Fri Jul 27 17:56:18 2018 +0200
SLING-7790 Initial implementation
---
.gitignore | 5 +
README.md | 9 +
pom.xml | 102 ++++++
.../provider/installhook/OsgiInstallerHook.java | 354 +++++++++++++++++++++
.../installhook/OsigInstallerListener.java | 87 +++++
5 files changed, 557 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8b0fe0c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/target/
+.project
+.classpath
+.settings/
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fdde553
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+
+ [![Build Status](https://builds.apache.org/buildStatus/icon?job=org.apache.sling.installer.provider.installhook-1.8)](https://builds.apache.org/view/S-Z/view/Sling/job/org.apache.sling.installer.provider.installhook-1.8) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/view/S-Z/view/Sling/job/org.apache.sling.installer.provider.installhook-1.8.svg)](https://builds.apache.org/view/S-Z/view/Sling/job/org.apache.sling.installer.provider.installhook-1.8/test_results_ [...]
+
+# Apache Sling JCR Installer Install Hook
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
+
+Allows to synchronously install bundles and configurations as contained in a vault package.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..820c8bb
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ you under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>30</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.installer.provider.installhook</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>Sling Installer Vault Package Install Hook</name>
+ <description>
+ Can be used in packages to ensure installation of bundles/configs during package installation (circumventing )
+ </description>
+
+ <properties>
+ <jackrabbit.version>2.10.0</jackrabbit.version>
+ <sling.java.version>7</sling.java.version>
+ <filevault.version>3.1.18</filevault.version>
+ <jackrabbit.version>2.10.1</jackrabbit.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>org.apache.sling.installer.provider.installhook.OsgiInstallerHook</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.jackrabbit.vault</groupId>
+ <artifactId>org.apache.jackrabbit.vault</artifactId>
+ <version>${filevault.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-jcr-commons</artifactId>
+ <version>${jackrabbit.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.installer.core</artifactId>
+ <version>3.6.8</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
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
new file mode 100644
index 0000000..de615ce
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/provider/installhook/OsgiInstallerHook.java
@@ -0,0 +1,354 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.sling.installer.provider.installhook;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.InstallContext;
+import org.apache.jackrabbit.vault.packaging.InstallHook;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+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.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+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 String PACKAGE_PROPERTY_MAX_WAIT_IN_SEC = "maxWaitForOsgiInstallerInSec";
+ private static final int DEFAULT_MAX_WAIT_IN_SEC = 120;
+
+ private static final String MANIFEST_BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName";
+ private static final String MANIFEST_BUNDLE_VERSION = "Bundle-Version";
+
+ private static final String JCR_CONTENT = "jcr:content";
+ private static final String JCR_CONTENT_DATA = JCR_CONTENT + "/jcr:data";
+ private static final String JCR_LAST_MODIFIED = "jcr:lastModified";
+ private static final String JCR_CONTENT_LAST_MODIFIED = JCR_CONTENT + "/" + JCR_LAST_MODIFIED;
+
+ public static final String URL_SCHEME = "jcrinstall";
+ public static final String CONFIG_SUFFIX = ".config";
+
+ private InstallHookLogger logger = new InstallHookLogger();
+
+ @Override
+ public void execute(InstallContext context) throws PackageException {
+
+ ServiceReference<OsgiInstaller> osgiInstallerServiceRef = null;
+ ServiceReference<ConfigurationAdmin> configAdminServiceRef = null;
+ ServiceRegistration<InstallationListener> hookInstallationListenerServiceRegistration = null;
+ try {
+ switch (context.getPhase()) {
+ case INSTALLED:
+ ImportOptions options = context.getOptions();
+ logger.setOptions(options);
+ VaultPackage vaultPackage = context.getPackage();
+
+ logger.log(getClass().getSimpleName() + " is active in " + vaultPackage.getId());
+
+ List<BundleInPackage> bundleResources = new ArrayList<>();
+ List<String> configResourcePaths = new ArrayList<>();
+ Archive archive = vaultPackage.getArchive();
+ collectResources(archive, archive.getRoot(), "", bundleResources, configResourcePaths);
+
+ 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);
+
+ configAdminServiceRef = getBundleContext().getServiceReference(ConfigurationAdmin.class);
+ ConfigurationAdmin confAdmin = (ConfigurationAdmin) getBundleContext()
+ .getService(configAdminServiceRef);
+
+ 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);
+
+ OsigInstallerListener hookInstallationListener = new OsigInstallerListener(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 = vaultPackage.getProperties()
+ .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 apply env vars: " + e, e);
+ } finally {
+ if (osgiInstallerServiceRef != null) {
+ getBundleContext().ungetService(osgiInstallerServiceRef);
+ }
+ if (configAdminServiceRef != null) {
+ getBundleContext().ungetService(configAdminServiceRef);
+ }
+
+ if (hookInstallationListenerServiceRegistration != null) {
+ hookInstallationListenerServiceRegistration.unregister();
+ }
+ }
+ }
+
+ private Set<String> getConfigPidsToInstall(List<String> configResourcePaths, Session session,
+ List<InstallableResource> installableResources, ConfigurationAdmin confAdmin)
+ throws IOException, InvalidSyntaxException, PathNotFoundException, 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 PathNotFoundException, 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;
+ }
+
+ private void collectResources(Archive archive, Entry entry, String dirPath, List<BundleInPackage> bundleResources,
+ List<String> configResources) {
+ String entryName = entry.getName();
+
+ if (entryName.endsWith(".jar") && dirPath.contains("/install")) {
+
+ 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);
+ String bundlePath = StringUtils.substringAfter(dirPath + entryName, "/jcr_root");
+ bundleResources.add(new BundleInPackage(bundlePath, symbolicName, version));
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "Could not read symbolic name and version from manifest of bundle " + entryName);
+ }
+ }
+
+ if (entryName.endsWith(CONFIG_SUFFIX) && dirPath.contains("/config")) {
+ String configPath = StringUtils.substringAfter(dirPath + entryName, "/jcr_root");
+ configResources.add(configPath);
+ }
+
+ for (Entry child : entry.getChildren()) {
+ collectResources(archive, child, dirPath + entryName + "/", bundleResources, configResources);
+ }
+ }
+
+ private 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, null);
+ }
+
+ // always get fresh bundle context to avoid "Dynamic class loader has already
+ // been deactivated" exceptions
+ public 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 + "]";
+ }
+
+ }
+
+ class InstallHookLogger {
+
+ private ImportOptions options;
+
+ public void setOptions(ImportOptions options) {
+ this.options = options;
+ }
+
+ public void logError(Logger logger, String message, Throwable throwable) {
+ ProgressTrackerListener listener = options.getListener();
+ 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) {
+ ProgressTrackerListener listener = options.getListener();
+ 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/OsigInstallerListener.java b/src/main/java/org/apache/sling/installer/provider/installhook/OsigInstallerListener.java
new file mode 100644
index 0000000..8cb83e7
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/provider/installhook/OsigInstallerListener.java
@@ -0,0 +1,87 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.sling.installer.provider.installhook;
+
+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;
+import org.apache.sling.installer.api.tasks.TaskResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OsigInstallerListener implements InstallationListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OsigInstallerListener.class);
+
+ private static final String ENTITY_ID_PREFIX_BUNDLE = "bundle:";
+ private static final String ENTITY_ID_PREFIX_CONFIG = "config:";
+
+ private final Set<String> requiredBundleSymbolicNames;
+ private final Set<String> requiredConfigPids;
+ private final Set<String> installedBundleSymbolicNames = new HashSet<>();
+ private final Set<String> installedConfigPids = new HashSet<>();
+
+ public OsigInstallerListener(Set<String> requiredBundleSymbolicNames, Set<String> requiredConfigPids) {
+ this.requiredBundleSymbolicNames = requiredBundleSymbolicNames;
+ this.requiredConfigPids = requiredConfigPids;
+ }
+
+ @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();
+
+ LOG.debug("Received event about processed entityId {}", entityId);
+
+ 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);
+ }
+ }
+ }
+
+ 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);
+
+ LOG.trace("requiredConfigPids: {}", requiredConfigPids);
+ LOG.trace("installedConfigPids: {}", installedConfigPids);
+ HashSet<String> configsLeftToInstall = new HashSet<String>(requiredConfigPids);
+ requiredConfigPids.removeAll(installedConfigPids);
+ LOG.debug("configsLeftToInstall: {}", configsLeftToInstall);
+
+ return bundlesLeftToInstall.isEmpty() && configsLeftToInstall.isEmpty();
+ }
+
+}