Author: gnodet Date: Mon Oct 12 22:55:29 2009 New Revision: 824529 URL: http://svn.apache.org/viewvc?rev=824529&view=rev Log: 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
Added: felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java Modified: felix/trunk/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java felix/trunk/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml felix/trunk/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java Modified: felix/trunk/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java?rev=824529&r1=824528&r2=824529&view=diff ============================================================================== --- felix/trunk/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java (original) +++ felix/trunk/karaf/features/command/src/main/java/org/apache/felix/karaf/features/command/InstallFeatureCommand.java Mon Oct 12 22:55:29 2009 @@ -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); } } Modified: felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java?rev=824529&r1=824528&r2=824529&view=diff ============================================================================== --- felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java (original) +++ felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/FeaturesService.java Mon Oct 12 22:55:29 2009 @@ -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; Modified: felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java?rev=824529&r1=824528&r2=824529&view=diff ============================================================================== --- felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java (original) +++ felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java Mon Oct 12 22:55:29 2009 @@ -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()) { Added: felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java?rev=824529&view=auto ============================================================================== --- felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java (added) +++ felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/HeaderParser.java Mon Oct 12 22:55:29 2009 @@ -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:d...@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(); + } + + } +} Added: felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java?rev=824529&view=auto ============================================================================== --- felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java (added) +++ felix/trunk/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/VersionRange.java Mon Oct 12 22:55:29 2009 @@ -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 Modified: felix/trunk/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml?rev=824529&r1=824528&r2=824529&view=diff ============================================================================== --- felix/trunk/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml (original) +++ felix/trunk/karaf/features/core/src/main/resources/OSGI-INF/blueprint/gshell-features.xml Mon Oct 12 22:55:29 2009 @@ -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> Modified: felix/trunk/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java URL: http://svn.apache.org/viewvc/felix/trunk/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java?rev=824529&r1=824528&r2=824529&view=diff ============================================================================== --- felix/trunk/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java (original) +++ felix/trunk/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java Mon Oct 12 22:55:29 2009 @@ -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);