improved feature installer logic (#238)
- upgraded to Java 8 - reduced calls to listInstalledFeatures, which is very expensive - properly do the calculation of current/target/diff list of addons, when the config changes - don't log errors when uninstallation fails as this can happen if it is a dependent feature that must not be uninstalled Signed-off-by: Kai Kreuzer <kai@openhab.org>pull/239/head
parent
0c7e762c03
commit
3f1af862bc
|
@ -12,6 +12,6 @@
|
|||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
|
|
@ -1,8 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
|
|
|
@ -25,8 +25,8 @@ import java.util.Properties;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.karaf.features.Feature;
|
||||
import org.apache.karaf.features.FeaturesService;
|
||||
|
@ -109,47 +109,58 @@ public class FeatureInstaller implements ConfigurationListener {
|
|||
}
|
||||
|
||||
protected void modified(final Map<String, Object> config) {
|
||||
if (config.get(CFG_REMOTE) == null && getOnlineStatus() == true) {
|
||||
// we seem to have an online distro and no configuration set
|
||||
updateOnlineConfigFlag(true);
|
||||
return;
|
||||
} else {
|
||||
boolean online = config.get(CFG_REMOTE) != null && "true".equals(config.get(CFG_REMOTE).toString());
|
||||
if (getOnlineStatus() != online) {
|
||||
setOnlineStatus(online);
|
||||
}
|
||||
boolean changed = false;
|
||||
boolean online = (config.get(CFG_REMOTE) == null && getOnlineStatus())
|
||||
|| (config.get(CFG_REMOTE) != null && "true".equals(config.get(CFG_REMOTE).toString()));
|
||||
if (getOnlineStatus() != online) {
|
||||
setOnlineStatus(online);
|
||||
}
|
||||
|
||||
final boolean configChanged = changed;
|
||||
if (configChanged) {
|
||||
// let's set the flag immediately, so that we do not miss the event
|
||||
paxCfgUpdated = false;
|
||||
}
|
||||
|
||||
final FeaturesService service = featuresService;
|
||||
ExecutorService scheduler = Executors.newSingleThreadExecutor();
|
||||
|
||||
scheduler.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int counter = 0;
|
||||
// wait up to 5 seconds for the config update event
|
||||
while (!paxCfgUpdated && counter++ < 50) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
setLegacyExtensions(service, config);
|
||||
installPackage(service, config);
|
||||
installAddons(service, config);
|
||||
scheduler.execute(() -> {
|
||||
if (configChanged) {
|
||||
waitForConfigUpdateEvent();
|
||||
}
|
||||
setLegacyExtensions(service, config);
|
||||
if (installPackage(service, config)) {
|
||||
// our package selection has changed, so let's wait for the values to be available in config admin
|
||||
// which we will receive as another call to modified
|
||||
return;
|
||||
}
|
||||
installAddons(service, config);
|
||||
});
|
||||
}
|
||||
|
||||
private void waitForConfigUpdateEvent() {
|
||||
int counter = 0;
|
||||
// wait up to 5 seconds for the config update event
|
||||
while (!paxCfgUpdated && counter++ < 50) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addAddon(String type, String id) {
|
||||
try {
|
||||
Configuration cfg = configurationAdmin.getConfiguration(OpenHAB.ADDONS_SERVICE_PID, null);
|
||||
Dictionary<String, Object> props = cfg.getProperties();
|
||||
Object typeProp = props.get(type);
|
||||
String[] addonIds = typeProp != null ? typeProp.toString().split(",") : new String[0];
|
||||
if (!ArrayUtils.contains(addonIds, id)) {
|
||||
ArrayList<String> newAddonIds = new ArrayList<>(addonIds.length + 1);
|
||||
newAddonIds.addAll(Arrays.asList(addonIds));
|
||||
List<String> trimmedAddonIds = Arrays.stream(addonIds).map(addonId -> addonId.trim())
|
||||
.collect(Collectors.toList());
|
||||
if (!trimmedAddonIds.contains(id)) {
|
||||
List<String> newAddonIds = new ArrayList<>(trimmedAddonIds.size() + 1);
|
||||
newAddonIds.addAll(trimmedAddonIds);
|
||||
newAddonIds.add(id);
|
||||
props.put(type, StringUtils.join(newAddonIds, ','));
|
||||
cfg.update(props);
|
||||
|
@ -170,8 +181,10 @@ public class FeatureInstaller implements ConfigurationListener {
|
|||
Dictionary<String, Object> props = cfg.getProperties();
|
||||
Object typeProp = props.get(type);
|
||||
String[] addonIds = typeProp != null ? typeProp.toString().split(",") : new String[0];
|
||||
if (ArrayUtils.contains(addonIds, id)) {
|
||||
ArrayList<String> newAddonIds = new ArrayList<>(Arrays.asList(addonIds));
|
||||
List<String> trimmedAddonIds = Arrays.stream(addonIds).map(addonId -> addonId.trim())
|
||||
.collect(Collectors.toList());
|
||||
if (trimmedAddonIds.contains(id)) {
|
||||
List<String> newAddonIds = new ArrayList<>(trimmedAddonIds);
|
||||
boolean success = newAddonIds.remove(id);
|
||||
props.put(type, StringUtils.join(newAddonIds, ','));
|
||||
cfg.update(props);
|
||||
|
@ -267,10 +280,10 @@ public class FeatureInstaller implements ConfigurationListener {
|
|||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private void setOnlineStatus(boolean status) {
|
||||
private boolean setOnlineStatus(boolean status) {
|
||||
boolean changed = false;
|
||||
if (online_repo_url != null) {
|
||||
try {
|
||||
Configuration paxCfg = configurationAdmin.getConfiguration(PAX_URL_PID, null);
|
||||
|
@ -288,84 +301,74 @@ public class FeatureInstaller implements ConfigurationListener {
|
|||
if (status) {
|
||||
if (!repoCfg.contains(online_repo_url)) {
|
||||
repoCfg.add(online_repo_url);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (repoCfg.contains(online_repo_url)) {
|
||||
repoCfg.remove(online_repo_url);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
properties.put(PROPERTY_MVN_REPOS, StringUtils.join(repoCfg.toArray(), ","));
|
||||
paxCfgUpdated = false;
|
||||
paxCfg.update(properties);
|
||||
if (changed) {
|
||||
properties.put(PROPERTY_MVN_REPOS, StringUtils.join(repoCfg.toArray(), ","));
|
||||
paxCfg.update(properties);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed setting the add-on management online/offline mode: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOnlineConfigFlag(final Boolean online) {
|
||||
// let's do this asynchronous to not block the current config dispatching
|
||||
ExecutorService scheduler = Executors.newSingleThreadExecutor();
|
||||
final ConfigurationAdmin service = configurationAdmin;
|
||||
|
||||
scheduler.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Configuration cfg = service.getConfiguration(OpenHAB.ADDONS_SERVICE_PID);
|
||||
Dictionary<String, Object> properties = cfg.getProperties();
|
||||
if (properties == null) {
|
||||
properties = new Hashtable<>();
|
||||
}
|
||||
if (properties.get(CFG_REMOTE) == null
|
||||
|| !online.toString().equals(properties.get(CFG_REMOTE).toString())) {
|
||||
// configuration is out of sync, so let's update it
|
||||
properties.put(CFG_REMOTE, online);
|
||||
cfg.update(properties);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed updating the remote configuration: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
}
|
||||
|
||||
private synchronized void installAddons(final FeaturesService service, final Map<String, Object> config) {
|
||||
Set<String> installAddons = new HashSet<>();
|
||||
Set<String> installedAddons = new HashSet<>();
|
||||
Set<String> uninstallAddons = new HashSet<>();
|
||||
|
||||
final Set<String> currentAddons = new HashSet<>(); // the currently installed ones
|
||||
final Set<String> targetAddons = new HashSet<>(); // the target we want to have installed afterwards
|
||||
final Set<String> installAddons = new HashSet<>(); // the ones to be installed (the diff)
|
||||
|
||||
for (String type : addonTypes) {
|
||||
Object install = config.get(type);
|
||||
if (install instanceof String) {
|
||||
String[] entries = ((String) install).split(",");
|
||||
for (String addon : entries) {
|
||||
if (!StringUtils.isEmpty(addon)) {
|
||||
String id = PREFIX + type + "-" + addon.trim();
|
||||
installedAddons.add(id);
|
||||
if (!isInstalled(service, id)) {
|
||||
installAddons.add(id);
|
||||
try {
|
||||
Feature[] features = featuresService.listInstalledFeatures();
|
||||
for (String addon : entries) {
|
||||
if (!StringUtils.isEmpty(addon)) {
|
||||
String id = PREFIX + type + "-" + addon.trim();
|
||||
targetAddons.add(id);
|
||||
if (!isInstalled(features, id)) {
|
||||
installAddons.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// we collect all possible addons first
|
||||
for (String addon : getAllAddonsOfType(type)) {
|
||||
if (!StringUtils.isEmpty(addon)) {
|
||||
uninstallAddons.add(PREFIX + type + "-" + addon.trim());
|
||||
|
||||
// we collect all installed addons
|
||||
for (String addon : getAllAddonsOfType(type)) {
|
||||
if (!StringUtils.isEmpty(addon)) {
|
||||
String id = PREFIX + type + "-" + addon.trim();
|
||||
if (isInstalled(features, id)) {
|
||||
currentAddons.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed retrieving features: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// now remove everything from the list that we want to keep installed
|
||||
for (String addon : installedAddons) {
|
||||
uninstallAddons.remove(addon);
|
||||
}
|
||||
|
||||
// now calculate what we have to uninstall: all current ones that are not part of the target anymore
|
||||
Set<String> uninstallAddons = currentAddons.stream().filter(addon -> !targetAddons.contains(addon))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// do the installation
|
||||
if (!installAddons.isEmpty()) {
|
||||
installFeatures(service, installAddons);
|
||||
}
|
||||
|
||||
// do the de-installation
|
||||
if (!uninstallAddons.isEmpty()) {
|
||||
for (String addon : uninstallAddons) {
|
||||
uninstallFeature(service, addon);
|
||||
}
|
||||
uninstallFeatures(service, uninstallAddons);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,65 +392,90 @@ public class FeatureInstaller implements ConfigurationListener {
|
|||
featuresService.installFeatures(addons,
|
||||
EnumSet.of(FeaturesService.Option.Upgrade, FeaturesService.Option.NoFailOnFeatureNotFound));
|
||||
logger.debug("Installed '{}'", StringUtils.join(addons, ", "));
|
||||
for (String addon : addons) {
|
||||
if (isInstalled(featuresService, addon)) {
|
||||
postInstalledEvent(addon);
|
||||
try {
|
||||
Feature[] features = featuresService.listInstalledFeatures();
|
||||
for (String addon : addons) {
|
||||
if (isInstalled(features, addon)) {
|
||||
postInstalledEvent(addon);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed retrieving features: {}", e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed installing '{}': {}", StringUtils.join(addons, ", "), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized void installFeature(FeaturesService featuresService, String name) {
|
||||
private synchronized void uninstallFeatures(FeaturesService featuresService, Set<String> addons) {
|
||||
for (String addon : addons) {
|
||||
uninstallFeature(featuresService, addon);
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized boolean installFeature(FeaturesService featuresService, String name) {
|
||||
try {
|
||||
if (!isInstalled(featuresService, name)) {
|
||||
Feature[] features = featuresService.listInstalledFeatures();
|
||||
if (!isInstalled(features, name)) {
|
||||
featuresService.installFeature(name);
|
||||
if (isInstalled(featuresService, name)) {
|
||||
features = featuresService.listInstalledFeatures();
|
||||
if (isInstalled(features, name)) {
|
||||
logger.debug("Installed '{}'", name);
|
||||
postInstalledEvent(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed installing '{}': {}", name, e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static synchronized void uninstallFeature(FeaturesService featuresService, String name) {
|
||||
private static synchronized boolean uninstallFeature(FeaturesService featuresService, String name) {
|
||||
try {
|
||||
if (isInstalled(featuresService, name)) {
|
||||
Feature[] features = featuresService.listInstalledFeatures();
|
||||
if (isInstalled(features, name)) {
|
||||
featuresService.uninstallFeature(name);
|
||||
logger.info("Uninstalled '{}'", name);
|
||||
postUninstalledEvent(name);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed uninstalling '{}': {}", name, e.getMessage());
|
||||
logger.debug("Failed uninstalling '{}': {}", name, e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static synchronized void installPackage(FeaturesService featuresService, final Map<String, Object> config) {
|
||||
private static synchronized boolean installPackage(FeaturesService featuresService,
|
||||
final Map<String, Object> config) {
|
||||
boolean configChanged = false;
|
||||
Object packageName = config.get(OpenHAB.CFG_PACKAGE);
|
||||
if (packageName instanceof String) {
|
||||
currentPackage = (String) packageName;
|
||||
String name = PREFIX + PREFIX_PACKAGE + ((String) packageName).trim();
|
||||
installFeature(featuresService, name);
|
||||
if (installFeature(featuresService, name)) {
|
||||
configChanged = true;
|
||||
}
|
||||
|
||||
// uninstall all other packages
|
||||
try {
|
||||
for (Feature feature : featuresService.listFeatures()) {
|
||||
if (feature.getName().startsWith(PREFIX + PREFIX_PACKAGE) && !feature.getName().equals(name)) {
|
||||
uninstallFeature(featuresService, feature.getName());
|
||||
if (uninstallFeature(featuresService, feature.getName()) && !configChanged) {
|
||||
configChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed retrieving features: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
private static boolean isInstalled(FeaturesService featuresService, String name) {
|
||||
private static boolean isInstalled(Feature[] features, String name) {
|
||||
try {
|
||||
for (Feature feature : featuresService.listInstalledFeatures()) {
|
||||
for (Feature feature : features) {
|
||||
if (feature.getName().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue