FELIX-1689: When deploying a feature, we need to refresh some bundles to cope with newly resolved optional dependencies
FELIX-1682: fix problem with bundles not being started after an installation failure
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@824529 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java
index 47dd4e6..45f7897 100644
--- a/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java
+++ b/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java
@@ -16,6 +16,8 @@
*/
package org.apache.felix.karaf.features.command;
+import java.util.EnumSet;
+
import org.apache.felix.gogo.commands.Option;
import org.apache.felix.karaf.features.FeaturesService;
import org.apache.felix.gogo.commands.Argument;
@@ -30,13 +32,22 @@
String name;
@Argument(index = 1, name = "version", description = "The version of the feature", required = false, multiValued = false)
String version;
- @Option(name = "-n", aliases = "--no-clean", description = "Do not uninstall bundles on failure", required = false, multiValued = false)
+ @Option(name = "-c", aliases = "--no-clean", description = "Do not uninstall bundles on failure", required = false, multiValued = false)
boolean noClean;
+ @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false)
+ boolean noRefresh;
protected void doExecute(FeaturesService admin) throws Exception {
if (version == null || version.length() == 0) {
version = DEFAULT_VERSION;
}
- admin.installFeature(name, version, !noClean);
+ EnumSet<FeaturesService.Option> options = EnumSet.of(FeaturesService.Option.PrintBundlesToRefresh);
+ if (noRefresh) {
+ options.add(FeaturesService.Option.NoAutoRefreshBundles);
+ }
+ if (noClean) {
+ options.add(FeaturesService.Option.NoCleanIfFailure);
+ }
+ admin.installFeature(name, version, options);
}
}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java
index f0052d1..461dc73 100644
--- a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java
@@ -17,12 +17,19 @@
package org.apache.felix.karaf.features;
import java.net.URI;
+import java.util.EnumSet;
/**
* The service managing features repositories.
*/
public interface FeaturesService {
+ enum Option {
+ NoCleanIfFailure,
+ PrintBundlesToRefresh,
+ NoAutoRefreshBundles,
+ }
+
void addRepository(URI url) throws Exception;
void removeRepository(URI url);
@@ -33,7 +40,7 @@
void installFeature(String name, String version) throws Exception;
- void installFeature(String name, String version, boolean cleanIfFailure) throws Exception;
+ void installFeature(String name, String version, EnumSet<Option> options) throws Exception;
void uninstallFeature(String name) throws Exception;
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
index d2a4e76..8620861 100644
--- a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
@@ -25,9 +25,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
+import java.util.EnumSet;
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;
@@ -55,6 +57,7 @@
import org.osgi.service.prefs.Preferences;
import org.osgi.service.prefs.PreferencesService;
import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,6 +77,7 @@
private BundleContext bundleContext;
private ConfigurationAdmin configAdmin;
private PackageAdmin packageAdmin;
+ private StartLevel startLevel;
private PreferencesService preferences;
private Set<URI> uris;
private Map<URI, RepositoryImpl> repositories = new HashMap<URI, RepositoryImpl>();
@@ -115,6 +119,14 @@
this.preferences = preferences;
}
+ public StartLevel getStartLevel() {
+ return startLevel;
+ }
+
+ public void setStartLevel(StartLevel startLevel) {
+ this.startLevel = startLevel;
+ }
+
public void registerListener(FeaturesListener listener) {
listeners.add(listener);
for (Repository repository : listRepositories()) {
@@ -196,10 +208,10 @@
}
public void installFeature(String name, String version) throws Exception {
- installFeature(name, version, true);
+ installFeature(name, version, EnumSet.noneOf(Option.class));
}
- public void installFeature(String name, String version, boolean cleanIfFailure) throws Exception {
+ public void installFeature(String name, String version, EnumSet<Option> options) throws Exception {
InstallationState state = new InstallationState();
Feature f = getFeature(name, version);
if (f == null) {
@@ -209,20 +221,66 @@
try {
// Install everything
doInstallFeature(state, f);
+ // Find bundles to refresh
+ boolean print = options.contains(Option.PrintBundlesToRefresh);
+ boolean refresh = !options.contains(Option.NoAutoRefreshBundles);
+ if (print || refresh) {
+ Set<Bundle> bundlesToRefresh = findBundlesToRefresh(state);
+ StringBuilder sb = new StringBuilder();
+ for (Bundle b : bundlesToRefresh) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(b.getSymbolicName()).append(" (").append(b.getBundleId()).append(")");
+ }
+ LOGGER.info("Bundles to refresh: {}", sb.toString());
+ if (!bundlesToRefresh.isEmpty()) {
+ if (print) {
+ if (refresh) {
+ System.out.println("Refreshing bundles " + sb.toString());
+ } else {
+ System.out.println("The following bundles may need to be refreshed: " + sb.toString());
+ }
+ }
+ if (refresh) {
+ LOGGER.info("Refreshing bundles: {}", sb.toString());
+ getPackageAdmin().refreshPackages(bundlesToRefresh.toArray(new Bundle[bundlesToRefresh.size()]));
+ }
+ }
+ }
// Start all bundles
for (Bundle b : state.bundles) {
- // do not start fragment bundles.
+ // do not start fragment bundles
Dictionary d = b.getHeaders();
String fragmentHostHeader = (String) d.get(Constants.FRAGMENT_HOST);
if (fragmentHostHeader == null || fragmentHostHeader.trim().length() == 0) {
- b.start();
+ // do not start bundles that are persistently stopped
+ if (state.installed.contains(b)
+ || (b.getState() != Bundle.STARTING && b.getState() != Bundle.ACTIVE
+ && getStartLevel().isBundlePersistentlyStarted(b))) {
+ b.start();
+ }
}
}
} catch (Exception e) {
- // uninstall everything
- if (cleanIfFailure) {
- for (Bundle b : state.bundles) {
- b.uninstall();
+ // cleanup on error
+ if (!options.contains(Option.NoCleanIfFailure)) {
+ // Uninstall everything
+ for (Bundle b : state.installed) {
+ try {
+ b.uninstall();
+ } catch (Exception e2) {
+ // Ignore
+ }
+ }
+ } else {
+ // Force start of bundles so that they are flagged as persistently started
+ for (Bundle b : state.installed) {
+ try {
+ b.start();
+ } catch (Exception e2) {
+ // Ignore
+ }
}
}
// rethrow exception
@@ -236,6 +294,7 @@
}
protected static class InstallationState {
+ final Set<Bundle> installed = new HashSet<Bundle>();
final Set<Bundle> bundles = new HashSet<Bundle>();
final Map<Feature, Set<Long>> features = new HashMap<Feature, Set<Long>>();
}
@@ -262,18 +321,91 @@
}
Set<Long> bundles = new HashSet<Long>();
for (String bundleLocation : feature.getBundles()) {
- try {
- Bundle b = installBundleIfNeeded(bundleLocation);
- state.bundles.add(b);
- bundles.add(b.getBundleId());
- } catch (BundleAlreadyInstalledException e) {
- bundles.add(e.getBundle().getBundleId());
- }
+ Bundle b = installBundleIfNeeded(state, bundleLocation);
+ bundles.add(b.getBundleId());
}
state.features.put(feature, bundles);
}
- protected Bundle installBundleIfNeeded(String bundleLocation) throws IOException, BundleException, BundleAlreadyInstalledException {
+ protected Set<Bundle> findBundlesToRefresh(InstallationState state) {
+ // First pass: include all bundles contained in these features
+ Set<Bundle> bundles = new HashSet<Bundle>(state.bundles);
+ bundles.removeAll(state.installed);
+ if (bundles.isEmpty()) {
+ return bundles;
+ }
+ // Second pass: for each bundle, check if there is any unresolved optional package that could be resolved
+ Map<Bundle, List<HeaderParser.PathElement>> imports = new HashMap<Bundle, List<HeaderParser.PathElement>>();
+ for (Iterator<Bundle> it = bundles.iterator(); it.hasNext();) {
+ Bundle b = it.next();
+ String importsStr = (String) b.getHeaders().get(Constants.IMPORT_PACKAGE);
+ if (importsStr == null) {
+ it.remove();
+ } else {
+ List<HeaderParser.PathElement> importsList = HeaderParser.parseHeader(importsStr);
+ for (Iterator<HeaderParser.PathElement> itp = importsList.iterator(); itp.hasNext();) {
+ HeaderParser.PathElement p = itp.next();
+ String resolution = p.getDirective(Constants.RESOLUTION_DIRECTIVE);
+ if (!Constants.RESOLUTION_OPTIONAL.equals(resolution)) {
+ itp.remove();
+ }
+ }
+ if (importsList.isEmpty()) {
+ it.remove();
+ } else {
+ imports.put(b, importsList);
+ }
+ }
+ }
+ if (bundles.isEmpty()) {
+ return bundles;
+ }
+ // Third pass: compute a list of packages that are exported by our bundles and see if
+ // some exported packages can be wired to the optional imports
+ List<HeaderParser.PathElement> exports = new ArrayList<HeaderParser.PathElement>();
+ for (Bundle b : state.installed) {
+ String exportsStr = (String) b.getHeaders().get(Constants.EXPORT_PACKAGE);
+ if (exportsStr != null) {
+ List<HeaderParser.PathElement> exportsList = HeaderParser.parseHeader(exportsStr);
+ exports.addAll(exportsList);
+ }
+ }
+ for (Iterator<Bundle> it = bundles.iterator(); it.hasNext();) {
+ Bundle b = it.next();
+ List<HeaderParser.PathElement> importsList = imports.get(b);
+ for (Iterator<HeaderParser.PathElement> itpi = importsList.iterator(); itpi.hasNext();) {
+ HeaderParser.PathElement pi = itpi.next();
+ boolean matching = false;
+ for (HeaderParser.PathElement pe : exports) {
+ if (pi.getName().equals(pe.getName())) {
+ String evStr = pe.getAttribute(Constants.VERSION_ATTRIBUTE);
+ String ivStr = pi.getAttribute(Constants.VERSION_ATTRIBUTE);
+ Version exported = evStr != null ? Version.parseVersion(evStr) : Version.emptyVersion;
+ VersionRange imported = ivStr != null ? VersionRange.parse(ivStr) : VersionRange.infiniteRange;
+ if (imported.isInRange(exported)) {
+ matching = true;
+ break;
+ }
+ }
+ }
+ if (!matching) {
+ itpi.remove();
+ }
+ }
+ if (importsList.isEmpty()) {
+ it.remove();
+ } else {
+ LOGGER.debug("Refeshing bundle {} ({}) to solve the following optional imports", b.getSymbolicName(), b.getBundleId());
+ for (HeaderParser.PathElement p : importsList) {
+ LOGGER.debug(" {}", p);
+ }
+
+ }
+ }
+ return bundles;
+ }
+
+ protected Bundle installBundleIfNeeded(InstallationState state, String bundleLocation) throws IOException, BundleException {
LOGGER.debug("Checking " + bundleLocation);
InputStream is;
try {
@@ -295,7 +427,8 @@
Version bv = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
if (v.equals(bv)) {
LOGGER.debug(" found installed bundle: " + b);
- throw new BundleAlreadyInstalledException(b);
+ state.bundles.add(b);
+ return b;
}
}
}
@@ -306,24 +439,15 @@
is = new BufferedInputStream(new URL(bundleLocation).openStream());
}
LOGGER.debug("Installing bundle " + bundleLocation);
- return getBundleContext().installBundle(bundleLocation, is);
+ Bundle b = getBundleContext().installBundle(bundleLocation, is);
+ state.bundles.add(b);
+ state.installed.add(b);
+ return b;
} finally {
is.close();
}
}
- protected static class BundleAlreadyInstalledException extends Exception {
- private final Bundle bundle;
-
- public BundleAlreadyInstalledException(Bundle bundle) {
- this.bundle = bundle;
- }
-
- public Bundle getBundle() {
- return bundle;
- }
- }
-
public void uninstallFeature(String name) throws Exception {
List<String> versions = new ArrayList<String>();
for (Feature f : installed.keySet()) {
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java
new file mode 100644
index 0000000..e06c636
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java
@@ -0,0 +1,204 @@
+/**
+ * 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.felix.karaf.features.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class to parse a standard OSGi header with paths.
+ *
+ * @author <a href="mailto:dev@geronimo.apache.org">Apache Geronimo Project</a>
+ * @version $Rev: 786132 $, $Date: 2009-06-18 17:47:58 +0200 (Thu, 18 Jun 2009) $
+ */
+public final class HeaderParser {
+
+ // Private constructor for static final class
+ private HeaderParser() {
+ }
+
+ /**
+ * Parse a given OSGi header into a list of paths
+ *
+ * @param header the OSGi header to parse
+ * @return the list of paths extracted from this header
+ */
+ public static List<PathElement> parseHeader(String header) {
+ List<PathElement> elements = new ArrayList<PathElement>();
+ if (header == null || header.trim().length() == 0) {
+ return elements;
+ }
+ String[] clauses = parseDelimitedString(header, ",");
+ for (String clause : clauses) {
+ String[] tokens = clause.split(";");
+ if (tokens.length < 1) {
+ throw new IllegalArgumentException("Invalid header clause: " + clause);
+ }
+ PathElement elem = new PathElement(tokens[0].trim());
+ elements.add(elem);
+ for (int i = 1; i < tokens.length; i++) {
+ int pos = tokens[i].indexOf('=');
+ if (pos != -1) {
+ if (pos > 0 && tokens[i].charAt(pos - 1) == ':') {
+ String name = tokens[i].substring(0, pos - 1).trim();
+ String value = tokens[i].substring(pos + 1).trim();
+ if (value.startsWith("\"") && value.endsWith("\"")) {
+ value = value.substring(1, value.length() - 1);
+ }
+ elem.addDirective(name, value);
+ } else {
+ String name = tokens[i].substring(0, pos).trim();
+ String value = tokens[i].substring(pos + 1).trim();
+ if (value.startsWith("\"") && value.endsWith("\"")) {
+ value = value.substring(1, value.length() - 1);
+ }
+ elem.addAttribute(name, value);
+ }
+ } else {
+ elem = new PathElement(tokens[i].trim());
+ elements.add(elem);
+ }
+ }
+ }
+ return elements;
+ }
+
+ /**
+ * Parses delimited string and returns an array containing the tokens. This
+ * parser obeys quotes, so the delimiter character will be ignored if it is
+ * inside of a quote. This method assumes that the quote character is not
+ * included in the set of delimiter characters.
+ * @param value the delimited string to parse.
+ * @param delim the characters delimiting the tokens.
+ * @return an array of string tokens or null if there were no tokens.
+ **/
+ public static String[] parseDelimitedString(String value, String delim)
+ {
+ if (value == null)
+ {
+ value = "";
+ }
+
+ List list = new ArrayList();
+
+ int CHAR = 1;
+ int DELIMITER = 2;
+ int STARTQUOTE = 4;
+ int ENDQUOTE = 8;
+
+ StringBuffer sb = new StringBuffer();
+
+ int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+ for (int i = 0; i < value.length(); i++)
+ {
+ char c = value.charAt(i);
+
+ boolean isDelimiter = (delim.indexOf(c) >= 0);
+ boolean isQuote = (c == '"');
+
+ if (isDelimiter && ((expecting & DELIMITER) > 0))
+ {
+ list.add(sb.toString().trim());
+ sb.delete(0, sb.length());
+ expecting = (CHAR | DELIMITER | STARTQUOTE);
+ }
+ else if (isQuote && ((expecting & STARTQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = CHAR | ENDQUOTE;
+ }
+ else if (isQuote && ((expecting & ENDQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = (CHAR | STARTQUOTE | DELIMITER);
+ }
+ else if ((expecting & CHAR) > 0)
+ {
+ sb.append(c);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid delimited string: " + value);
+ }
+ }
+
+ if (sb.length() > 0)
+ {
+ list.add(sb.toString().trim());
+ }
+
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+ public static class PathElement {
+
+ private String path;
+ private Map<String, String> attributes;
+ private Map<String, String> directives;
+
+ public PathElement(String path) {
+ this.path = path;
+ this.attributes = new HashMap<String, String>();
+ this.directives = new HashMap<String, String>();
+ }
+
+ public String getName() {
+ return this.path;
+ }
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public String getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+ public void addAttribute(String name, String value) {
+ attributes.put(name, value);
+ }
+
+ public Map<String, String> getDirectives() {
+ return directives;
+ }
+
+ public String getDirective(String name) {
+ return directives.get(name);
+ }
+
+ public void addDirective(String name, String value) {
+ directives.put(name, value);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(this.path);
+ for (Map.Entry<String,String> directive : this.directives.entrySet()) {
+ sb.append(";").append(directive.getKey()).append(":=").append(directive.getValue());
+ }
+ for (Map.Entry<String,String> attribute : this.attributes.entrySet()) {
+ sb.append(";").append(attribute.getKey()).append("=").append(attribute.getValue());
+ }
+ return sb.toString();
+ }
+
+ }
+}
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java
new file mode 100644
index 0000000..f600b8f
--- /dev/null
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java
@@ -0,0 +1,158 @@
+/*
+ * 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.felix.karaf.features.internal;
+
+import org.osgi.framework.Version;
+
+public class VersionRange
+{
+ private final Version m_low;
+ private final boolean m_isLowInclusive;
+ private final Version m_high;
+ private final boolean m_isHighInclusive;
+ public static final VersionRange infiniteRange = new VersionRange(Version.emptyVersion, true, null, true);
+
+ public VersionRange(
+ Version low, boolean isLowInclusive,
+ Version high, boolean isHighInclusive)
+ {
+ m_low = low;
+ m_isLowInclusive = isLowInclusive;
+ m_high = high;
+ m_isHighInclusive = isHighInclusive;
+ }
+
+ public Version getLow()
+ {
+ return m_low;
+ }
+
+ public boolean isLowInclusive()
+ {
+ return m_isLowInclusive;
+ }
+
+ public Version getHigh()
+ {
+ return m_high;
+ }
+
+ public boolean isHighInclusive()
+ {
+ return m_isHighInclusive;
+ }
+
+ public boolean isInRange(Version version)
+ {
+ // We might not have an upper end to the range.
+ if (m_high == null)
+ {
+ return (version.compareTo(m_low) >= 0);
+ }
+ else if (isLowInclusive() && isHighInclusive())
+ {
+ return (version.compareTo(m_low) >= 0) && (version.compareTo(m_high) <= 0);
+ }
+ else if (isHighInclusive())
+ {
+ return (version.compareTo(m_low) > 0) && (version.compareTo(m_high) <= 0);
+ }
+ else if (isLowInclusive())
+ {
+ return (version.compareTo(m_low) >= 0) && (version.compareTo(m_high) < 0);
+ }
+ return (version.compareTo(m_low) > 0) && (version.compareTo(m_high) < 0);
+ }
+
+ public static VersionRange parse(String range)
+ {
+ // Check if the version is an interval.
+ if (range.indexOf(',') >= 0)
+ {
+ String s = range.substring(1, range.length() - 1);
+ String vlo = s.substring(0, s.indexOf(',')).trim();
+ String vhi = s.substring(s.indexOf(',') + 1, s.length()).trim();
+ return new VersionRange (
+ new Version(vlo), (range.charAt(0) == '['),
+ new Version(vhi), (range.charAt(range.length() - 1) == ']'));
+ }
+ else
+ {
+ return new VersionRange(new Version(range), true, null, false);
+ }
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final VersionRange other = (VersionRange) obj;
+ if (m_low != other.m_low && (m_low == null || !m_low.equals(other.m_low)))
+ {
+ return false;
+ }
+ if (m_isLowInclusive != other.m_isLowInclusive)
+ {
+ return false;
+ }
+ if (m_high != other.m_high && (m_high == null || !m_high.equals(other.m_high)))
+ {
+ return false;
+ }
+ if (m_isHighInclusive != other.m_isHighInclusive)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int hash = 5;
+ hash = 97 * hash + (m_low != null ? m_low.hashCode() : 0);
+ hash = 97 * hash + (m_isLowInclusive ? 1 : 0);
+ hash = 97 * hash + (m_high != null ? m_high.hashCode() : 0);
+ hash = 97 * hash + (m_isHighInclusive ? 1 : 0);
+ return hash;
+ }
+
+ public String toString()
+ {
+ if (m_high != null)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append(m_isLowInclusive ? '[' : '(');
+ sb.append(m_low.toString());
+ sb.append(',');
+ sb.append(m_high.toString());
+ sb.append(m_isHighInclusive ? ']' : ')');
+ return sb.toString();
+ }
+ else
+ {
+ return m_low.toString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml b/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml
index f007b65..87f6a39 100644
--- a/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml
+++ b/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml
@@ -36,6 +36,7 @@
<property name="configAdmin" ref="configAdmin" />
<property name="packageAdmin" ref="packageAdmin" />
<property name="preferences" ref="preferences" />
+ <property name="startLevel" ref="startLevel" />
<property name="bundleContext" ref="blueprintBundleContext" />
</bean>
@@ -51,6 +52,8 @@
<reference id="packageAdmin" interface="org.osgi.service.packageadmin.PackageAdmin" />
+ <reference id="startLevel" interface="org.osgi.service.startlevel.StartLevel" />
+
<service ref="featuresService" interface="org.apache.felix.karaf.features.FeaturesService" />
</blueprint>
diff --git a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
index f467447..008fb13 100644
--- a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
+++ b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
@@ -21,6 +21,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URI;
+import java.util.EnumSet;
import java.util.Hashtable;
import junit.framework.TestCase;
@@ -121,7 +122,7 @@
replay(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle);
- svc.installFeature("f1");
+ svc.installFeature("f1", FeatureImpl.DEFAULT_VERSION, EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
Feature[] installed = svc.listInstalledFeatures();
assertEquals(1, installed.length);
@@ -256,8 +257,8 @@
// ok
}
- svc.installFeature("f1", "0.1");
- svc.installFeature("f1", "0.2");
+ svc.installFeature("f1", "0.1", EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
+ svc.installFeature("f1", "0.2", EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
try {
svc.uninstallFeature("f1");
@@ -397,6 +398,7 @@
expect(installedBundle.getBundleId()).andReturn(12345L);
expect(bundleContext.getBundle(12345L)).andReturn(installedBundle);
expect(installedBundle.getHeaders()).andReturn(new Hashtable());
+
installedBundle.start();
expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
@@ -466,6 +468,8 @@
prefs.putBoolean("bootFeaturesInstalled", false);
prefs.flush();
+ expect(installedBundle.getHeaders()).andReturn(new Hashtable()).anyTimes();
+
// uninstallAllFeatures
expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);