[KARAF-5272] Enhance the features deployer so that it performs a real upgrade
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/eb996069 Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/eb996069 Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/eb996069 Branch: refs/heads/master Commit: eb9960695d978b0d6c817202e688a4e4a0f6da45 Parents: bc9adda Author: Guillaume Nodet <[email protected]> Authored: Tue Jul 25 15:34:43 2017 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Tue Jul 25 21:40:14 2017 +0200 ---------------------------------------------------------------------- deployer/features/pom.xml | 1 + .../features/FeatureDeploymentListener.java | 216 ++++++++----------- .../apache/karaf/features/FeaturesService.java | 6 + .../internal/service/FeaturesServiceImpl.java | 99 +++++++-- .../internal/service/RepositoryCache.java | 6 +- .../karaf/kar/internal/KarServiceImpl.java | 4 +- 6 files changed, 186 insertions(+), 146 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/eb996069/deployer/features/pom.xml ---------------------------------------------------------------------- diff --git a/deployer/features/pom.xml b/deployer/features/pom.xml index 0ff151a..d0c00bb 100644 --- a/deployer/features/pom.xml +++ b/deployer/features/pom.xml @@ -105,6 +105,7 @@ org.apache.karaf.deployer.features, org.apache.karaf.deployer.features.osgi, org.apache.karaf.util, + org.apache.felix.utils.version </Private-Package> </instructions> </configuration> http://git-wip-us.apache.org/repos/asf/karaf/blob/eb996069/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java ---------------------------------------------------------------------- diff --git a/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java b/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java index ab954fa..58bfc2b 100644 --- a/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java +++ b/deployer/features/src/main/java/org/apache/karaf/deployer/features/FeatureDeploymentListener.java @@ -23,25 +23,27 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.Enumeration; -import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; import org.apache.felix.fileinstall.ArtifactUrlTransformer; +import org.apache.felix.utils.version.VersionRange; import org.apache.karaf.features.Feature; import org.apache.karaf.features.FeaturesNamespaces; import org.apache.karaf.features.FeaturesService; @@ -50,9 +52,8 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleListener; -import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; + +import static org.apache.karaf.features.FeaturesService.ROOT_REGION; /** * A deployment listener able to hot deploy a feature descriptor @@ -63,7 +64,7 @@ public class FeatureDeploymentListener implements ArtifactUrlTransformer, Bundle private final Logger logger = LoggerFactory.getLogger(FeatureDeploymentListener.class); - private DocumentBuilderFactory dbf; + private XMLInputFactory xif; private FeaturesService featuresService; private BundleContext bundleContext; private Properties properties = new Properties(); @@ -144,9 +145,9 @@ public class FeatureDeploymentListener implements ArtifactUrlTransformer, Bundle public boolean canHandle(File artifact) { try { if (artifact.isFile() && artifact.getName().endsWith(".xml")) { - Document doc = parse(artifact); - String name = doc.getDocumentElement().getLocalName(); - String uri = doc.getDocumentElement().getNamespaceURI(); + QName qname = getRootElementName(artifact); + String name = qname.getLocalPart(); + String uri = qname.getNamespaceURI(); if ("features".equals(name) ) { if(isKnownFeaturesURI(uri)){ return true; @@ -176,125 +177,94 @@ public class FeatureDeploymentListener implements ArtifactUrlTransformer, Bundle } } - public void bundleChanged(BundleEvent bundleEvent) { - Bundle bundle = bundleEvent.getBundle(); - if (bundleEvent.getType() == BundleEvent.RESOLVED) { - try { - List<URL> urls = new ArrayList<>(); - Enumeration featuresUrlEnumeration = bundle.findEntries("/META-INF/" + FEATURE_PATH + "/", "*.xml", false); - while (featuresUrlEnumeration != null && featuresUrlEnumeration.hasMoreElements()) { - URL url = (URL) featuresUrlEnumeration.nextElement(); - try { - featuresService.addRepository(url.toURI()); - URI needRemovedRepo = null; - for (Repository repo : featuresService.listRepositories()) { - if (repo.getURI().equals(url.toURI())) { - Set<Feature> features = new HashSet<>(Arrays.asList(repo.getFeatures())); - Set<String> autoInstallFeatures = new HashSet<>(); - for(Feature feature:features) { - if(feature.getInstall() != null && feature.getInstall().equals(Feature.DEFAULT_INSTALL_MODE)){ - if (!featuresService.isInstalled(feature)) { - autoInstallFeatures.add(feature.getId()); - } - } - } - if (!autoInstallFeatures.isEmpty()) { - featuresService.installFeatures(autoInstallFeatures, EnumSet.noneOf(FeaturesService.Option.class)); - } - } else { - //remove older out-of-data feature repo - if (repo.getURI().toString().contains(FEATURE_PATH)) { - String featureFileName = repo.getURI().toString(); - featureFileName = featureFileName.substring(featureFileName.lastIndexOf('/') + 1); - String newFeatureFileName = url.toURI().toString(); - newFeatureFileName = newFeatureFileName.substring(newFeatureFileName.lastIndexOf('/') + 1); - if (featureFileName.equals(newFeatureFileName)) { - needRemovedRepo = repo.getURI(); - } - } - } + public synchronized void bundleChanged(BundleEvent bundleEvent) { + // Only handle resolved and uninstalled events + if (bundleEvent.getType() != BundleEvent.RESOLVED + && bundleEvent.getType() != BundleEvent.UNINSTALLED) { + return; + } + Bundle bundle = bundleEvent.getBundle(); + try { + // Remove previous informations + List<URI> repsToRemove = new ArrayList<>(); + List<String> reqsToRemove = new ArrayList<>(); + // Remove old properties + String prefix = "bundle." + bundle.getBundleId(); + String countStr = (String) properties.remove(prefix + ".reps.count"); + if (countStr != null) { + int count = Integer.parseInt(countStr); + for (int i = 0; i < count; i++) { + String rep = (String) properties.remove(prefix + ".reps.item" + i); + repsToRemove.add(URI.create(rep)); + } + } + countStr = (String) properties.remove(prefix + ".reqs.count"); + if (countStr != null) { + int count = Integer.parseInt(countStr); + for (int i = 0; i < count; i++) { + String req = (String) properties.remove(prefix + ".reqs.item" + i); + reqsToRemove.add(req); + } + } + saveProperties(); - } - urls.add(url); - if (needRemovedRepo != null) { - featuresService.removeRepository(needRemovedRepo); - } - } catch (Exception e) { - logger.error("Unable to install features", e); - } - } - synchronized (this) { - String prefix = bundle.getSymbolicName() + "-" + bundle.getVersion(); - String old = (String) properties.get(prefix + ".count"); - if (old != null && urls.isEmpty()) { - properties.remove(prefix + ".count"); - saveProperties(); - } else if (!urls.isEmpty()) { - properties.put(prefix + ".count", Integer.toString(urls.size())); - for (int i = 0; i < urls.size(); i++) { - properties.put(prefix + ".url." + i, urls.get(i).toExternalForm()); - } - saveProperties(); - } - } - } catch (Exception e) { - logger.error("Unable to install deployed features for bundle: " + bundle.getSymbolicName() + " - " + bundle.getVersion(), e); + // Compute new informations + List<URI> repsToAdd = new ArrayList<>(); + List<String> reqsToAdd = new ArrayList<>(); + if (bundleEvent.getType() == BundleEvent.RESOLVED) { + Enumeration featuresUrlEnumeration = bundle.findEntries("/META-INF/" + FEATURE_PATH + "/", "*.xml", false); + while (featuresUrlEnumeration != null && featuresUrlEnumeration.hasMoreElements()) { + URL url = (URL) featuresUrlEnumeration.nextElement(); + URI uri = url.toURI(); + repsToAdd.add(uri); + Repository rep = featuresService.createRepository(uri); + Stream.of(rep.getFeatures()) + .filter(f -> f.getInstall() == null || Feature.DEFAULT_INSTALL_MODE.equals(f.getInstall())) + .map(f -> "feature:" + f.getName() + "/" + new VersionRange(f.getVersion(), true)) + .forEach(reqsToAdd::add); } - } else if (bundleEvent.getType() == BundleEvent.UNINSTALLED) { - try { - synchronized (this) { - String prefix = bundle.getSymbolicName() + "-" + bundle.getVersion(); - String countStr = (String) properties.remove(prefix + ".count"); - if (countStr != null) { - int count = Integer.parseInt(countStr); - for (int i = 0; i < count; i++) { - URL url = new URL((String) properties.remove(prefix + ".url." + i)); - for (Repository repo : featuresService.listRepositories()) { - try { - if (repo.getURI().equals(url.toURI())) { - for (Feature f : repo.getFeatures()) { - try { - featuresService.uninstallFeature(f.getName(), f.getVersion()); - } catch (Exception e) { - logger.error("Unable to uninstall feature: " + f.getName(), e); - } - } - } - } catch (Exception e) { - logger.error("Unable to uninstall features: " + url, e); - } - } - try { - featuresService.removeRepository(url.toURI()); - } catch (URISyntaxException e) { - logger.error("Unable to remove repository: " + url, e); - } - } - saveProperties(); - } + if (!repsToAdd.isEmpty()) { + properties.put(prefix + ".reps.count", Integer.toString(repsToAdd.size())); + for (int i = 0; i < repsToAdd.size(); i++) { + properties.put(prefix + ".reps.item" + i, repsToAdd.get(i).toASCIIString()); + } + properties.put(prefix + ".reqs.count", Integer.toString(reqsToAdd.size())); + for (int i = 0; i < reqsToAdd.size(); i++) { + properties.put(prefix + ".reqs.item" + i, reqsToAdd.get(i)); } - } catch (Exception e) { - logger.error("Unable to uninstall deployed features for bundle: " + bundle.getSymbolicName() + " - " + bundle.getVersion(), e); } } + saveProperties(); + + // Call features service + List<Repository> requiredRepos = Arrays.asList(featuresService.listRequiredRepositories()); + Set<URI> requiredReposUris = requiredRepos.stream() + .map(Repository::getURI).collect(Collectors.toSet()); + requiredReposUris.removeAll(repsToRemove); + requiredReposUris.addAll(repsToAdd); + + Map<String, Set<String>> requirements = featuresService.listRequirements(); + requirements.get(ROOT_REGION).removeAll(reqsToRemove); + requirements.get(ROOT_REGION).addAll(reqsToAdd); + + if (!reqsToRemove.isEmpty() || !reqsToAdd.isEmpty()) { + featuresService.updateReposAndRequirements(requiredReposUris, requirements, EnumSet.noneOf(FeaturesService.Option.class)); + } + } catch (Exception e) { + logger.error("Unable to update deployed features for bundle: " + bundle.getSymbolicName() + " - " + bundle.getVersion(), e); + } } - protected Document parse(File artifact) throws Exception { - if (dbf == null) { - dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); + private QName getRootElementName(File artifact) throws Exception { + if (xif == null) { + xif = XMLInputFactory.newFactory(); + xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true); + } + try (InputStream is = new FileInputStream(artifact)) { + XMLStreamReader sr = xif.createXMLStreamReader(is); + sr.nextTag(); + return sr.getName(); } - DocumentBuilder db = dbf.newDocumentBuilder(); - db.setErrorHandler(new ErrorHandler() { - public void warning(SAXParseException exception) throws SAXException { - } - public void error(SAXParseException exception) throws SAXException { - } - public void fatalError(SAXParseException exception) throws SAXException { - throw exception; - } - }); - return db.parse(artifact); } } http://git-wip-us.apache.org/repos/asf/karaf/blob/eb996069/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java index c8cdd15..29c9093 100644 --- a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java +++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java @@ -126,6 +126,12 @@ public interface FeaturesService { void updateFeaturesState(Map<String, Map<String, FeatureState>> stateChanges, EnumSet<Option> options) throws Exception; + void updateReposAndRequirements(Set<URI> repos, + Map<String, Set<String>> requirements, + EnumSet<Option> options) throws Exception; + + Repository createRepository(URI uri) throws Exception; + Feature[] listFeatures() throws Exception; Feature[] listRequiredFeatures() throws Exception; http://git-wip-us.apache.org/repos/asf/karaf/blob/eb996069/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java index 353d40d..bd98364 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java @@ -42,12 +42,14 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.felix.utils.manifest.Clause; import org.apache.felix.utils.version.VersionCleaner; import org.apache.felix.utils.version.VersionRange; import org.apache.felix.utils.version.VersionTable; @@ -178,7 +180,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall // Resolve try { Map<String, Map<String, FeatureState>> stateChanges = Collections.emptyMap(); - doProvisionInThread(requestedFeatures, stateChanges, copyState(), options); + doProvisionInThread(requestedFeatures, stateChanges, copyState(), getFeaturesById(), options); } catch (Exception e) { LOGGER.warn("Error updating state", e); } @@ -645,6 +647,11 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall return map; } + protected Map<String, Feature> getFeaturesById() throws Exception { + return getFeatureCache().values().stream().flatMap(m -> m.values().stream()) + .collect(Collectors.toMap(Feature::getId, Function.identity())); + } + // // Installed features // @@ -858,7 +865,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall } print("Adding features: " + join(featuresToDisplay), options.contains(Option.Verbose)); Map<String, Map<String, FeatureState>> stateChanges = Collections.emptyMap(); - doProvisionInThread(required, stateChanges, state, options); + doProvisionInThread(required, stateChanges, state, getFeaturesById(), options); } @Override @@ -909,13 +916,13 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall required.remove(region); } Map<String, Map<String, FeatureState>> stateChanges = Collections.emptyMap(); - doProvisionInThread(required, stateChanges, state, options); + doProvisionInThread(required, stateChanges, state, getFeaturesById(), options); } @Override public void updateFeaturesState(Map<String, Map<String, FeatureState>> stateChanges, EnumSet<Option> options) throws Exception { State state = copyState(); - doProvisionInThread(copy(state.requirements), stateChanges, state, options); + doProvisionInThread(copy(state.requirements), stateChanges, state, getFeaturesById(), options); } @Override @@ -924,7 +931,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall Map<String, Set<String>> required = copy(state.requirements); add(required, requirements); Map<String, Map<String, FeatureState>> stateChanges = Collections.emptyMap(); - doProvisionInThread(required, stateChanges, state, options); + doProvisionInThread(required, stateChanges, state, getFeaturesById(), options); } @Override @@ -933,7 +940,63 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall Map<String, Set<String>> required = copy(state.requirements); remove(required, requirements); Map<String, Map<String, FeatureState>> stateChanges = Collections.emptyMap(); - doProvisionInThread(required, stateChanges, state, options); + doProvisionInThread(required, stateChanges, state, getFeaturesById(), options); + } + + @Override + public void updateReposAndRequirements(Set<URI> repos, Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception { + State stateCopy; + synchronized (lock) { + // Remove repo + Set<String> reps = repos.stream().map(URI::toString).collect(Collectors.toSet()); + Set<String> toRemove = diff(state.repositories, reps); + Set<String> toAdd = diff(reps, state.repositories); + state.repositories.removeAll(toRemove); + state.repositories.addAll(toAdd); + featureCache = null; + for (String uri : toRemove) { + repositories.removeRepository(URI.create(uri)); + } + for (String uri : toAdd) { + repositories.addRepository(createRepository(URI.create(uri))); + } + saveState(); + stateCopy = state.copy(); + } + Map<String, Map<String, FeatureState>> stateChanges = Collections.emptyMap(); + doProvisionInThread(requirements, stateChanges, stateCopy, getFeaturesById(), options); + } + + private <T> Set<T> diff(Set<T> s1, Set<T> s2) { + Set<T> s = new HashSet<>(s1); + s.removeAll(s2); + return s; + } + + @Override + public Repository createRepository(URI uri) throws Exception { + return repositories.create(uri, true, true); + } + + private Map<String, Feature> loadAllFeatures(Set<URI> uris) throws Exception { + //the outer map's key is feature name, the inner map's key is feature version + Map<String, Feature> map = new HashMap<>(); + // Two phase load: + // * first load dependent repositories + Set<URI> loaded = new HashSet<>(); + List<URI> toLoad = new ArrayList<>(uris); + Clause[] blacklisted = repositories.getBlacklisted(); + while (!toLoad.isEmpty()) { + URI uri = toLoad.remove(0); + if (loaded.add(uri)) { + Repository repo = new RepositoryImpl(uri, blacklisted); + Collections.addAll(toLoad, repo.getRepositories()); + for (Feature f : repo.getFeatures()) { + map.put(f.getId(), f); + } + } + } + return map; } @Override @@ -974,12 +1037,13 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall private void doProvisionInThread(final Map<String, Set<String>> requirements, final Map<String, Map<String, FeatureState>> stateChanges, final State state, + final Map<String, Feature> featureById, final EnumSet<Option> options) throws Exception { try { final String outputFile = this.outputFile.get(); this.outputFile.set(null); executor.submit(() -> { - doProvision(requirements, stateChanges, state, options, outputFile); + doProvision(requirements, stateChanges, state, featureById, options, outputFile); return null; }).get(); } catch (ExecutionException e) { @@ -996,7 +1060,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall } } - private Deployer.DeploymentState getDeploymentState(State state) throws Exception { + private Deployer.DeploymentState getDeploymentState(State state, Map<String, Feature> featuresById) throws Exception { Deployer.DeploymentState dstate = new Deployer.DeploymentState(); dstate.state = state; FrameworkInfo info = installSupport.getInfo(); @@ -1005,13 +1069,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall dstate.currentStartLevel = info.currentStartLevel; dstate.bundles = info.bundles; // Features - dstate.features = new HashMap<>(); - for (Map<String, Feature> m : getFeatureCache().values()) { - for (Feature feature : m.values()) { - String id = feature.getId(); - dstate.features.put(id, feature); - } - } + dstate.features = featuresById; RegionDigraph regionDigraph = installSupport.getDiGraphCopy(); dstate.bundlesPerRegion = DigraphHelper.getBundlesPerRegion(regionDigraph); dstate.filtersPerRegion = DigraphHelper.getPolicies(regionDigraph); @@ -1034,10 +1092,11 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall } private void doProvision(Map<String, Set<String>> requirements, // all requirements - Map<String, Map<String, FeatureState>> stateChanges, // features state changes - State state, // current state - EnumSet<Option> options, // installation options - String outputFile // file to store the resolution or null + Map<String, Map<String, FeatureState>> stateChanges, // features state changes + State state, // current state + Map<String, Feature> featuresById, // features by id + EnumSet<Option> options, // installation options + String outputFile // file to store the resolution or null ) throws Exception { Dictionary<String, String> props = getMavenConfig(); @@ -1049,7 +1108,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall Set<String> prereqs = new HashSet<>(); while (true) { try { - Deployer.DeploymentState dstate = getDeploymentState(state); + Deployer.DeploymentState dstate = getDeploymentState(state, featuresById); Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options, outputFile); new Deployer(manager, this.resolver, this).deploy(dstate, request); break; http://git-wip-us.apache.org/repos/asf/karaf/blob/eb996069/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java index 54cf04f..f92b0bc 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/RepositoryCache.java @@ -39,7 +39,11 @@ public class RepositoryCache { public RepositoryCache(String blacklisted) { this.blacklisted = loadBlacklist(blacklisted); } - + + public Clause[] getBlacklisted() { + return blacklisted; + } + private static Clause[] loadBlacklist(String blacklisted) { Set<String> blacklistStrings = Blacklist.loadBlacklist(blacklisted); return Parser.parseClauses(blacklistStrings.toArray(new String[blacklistStrings.size()])); http://git-wip-us.apache.org/repos/asf/karaf/blob/eb996069/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java ---------------------------------------------------------------------- diff --git a/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java b/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java index 743e74a..30f7279 100644 --- a/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java +++ b/kar/src/main/java/org/apache/karaf/kar/internal/KarServiceImpl.java @@ -274,7 +274,7 @@ public class KarServiceImpl implements KarService { if (repository.getURI().equals(karFeatureRepoUri)) { try { for (Feature feature : repository.getFeatures()) { - if (feature.getInstall() == null || !feature.getInstall().equals("manual")) { + if (feature.getInstall() == null || Feature.DEFAULT_INSTALL_MODE.equals(feature.getInstall())) { try { LOGGER.debug("noAutoRefreshBundles is " + isNoAutoRefreshBundles()); if (isNoAutoRefreshBundles()) { @@ -430,7 +430,7 @@ public class KarServiceImpl implements KarService { if (repository.getURI().equals(karFeatureRepoUri)) { try { for (Feature feature : repository.getFeatures()) { - if (feature.getInstall() == null || !feature.getInstall().equals("manual")) { + if (feature.getInstall() == null || Feature.DEFAULT_INSTALL_MODE.equals(feature.getInstall())) { try { featuresService.uninstallFeature(feature.getName(), feature.getVersion()); } catch (Exception e) {
