[KARAF-3003] Allow deployment based on generic requirements, bundles and bundle dependencies
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/98123912 Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/98123912 Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/98123912 Branch: refs/heads/master Commit: 981239128f69f31dbd89cb6ddabf8a416d1606e1 Parents: 714da5e Author: Guillaume Nodet <[email protected]> Authored: Mon May 26 22:44:36 2014 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Mon May 26 22:44:36 2014 +0200 ---------------------------------------------------------------------- .../karaf/features/command/RequirementAdd.java | 85 ++++++++++++ .../karaf/features/command/RequirementList.java | 59 +++++++++ .../features/command/RequirementRemove.java | 85 ++++++++++++ .../apache/karaf/features/FeaturesService.java | 6 + .../features/internal/region/Subsystem.java | 130 ++++++++++++++----- .../internal/region/SubsystemResolver.java | 19 +-- .../internal/resolver/ResourceUtils.java | 26 ++++ .../features/internal/service/Deployer.java | 16 +-- .../internal/service/FeaturesServiceImpl.java | 84 ++++++++---- .../karaf/features/internal/service/State.java | 6 +- .../features/internal/service/StateStorage.java | 6 +- .../features/internal/region/SubsystemTest.java | 24 ++++ .../features/internal/service/DeployerTest.java | 21 ++- .../internal/service/StateStorageTest.java | 4 +- 14 files changed, 472 insertions(+), 99 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java ---------------------------------------------------------------------- diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java b/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java new file mode 100644 index 0000000..c8491b3 --- /dev/null +++ b/features/command/src/main/java/org/apache/karaf/features/command/RequirementAdd.java @@ -0,0 +1,85 @@ +/* + * 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.karaf.features.command; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.karaf.features.FeaturesService; +import org.apache.karaf.shell.api.action.Action; +import org.apache.karaf.shell.api.action.Argument; +import org.apache.karaf.shell.api.action.Command; +import org.apache.karaf.shell.api.action.Option; +import org.apache.karaf.shell.api.action.lifecycle.Reference; +import org.apache.karaf.shell.api.action.lifecycle.Service; + +@Command(scope = "feature", name = "requirement-add", description = "Add provisioning requirements.") +@Service +public class RequirementAdd implements Action { + + @Reference + private FeaturesService featuresService; + + @Argument(required = true, multiValued = true) + List<String> requirements; + + @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false) + boolean noRefresh; + + @Option(name = "-s", aliases = "--no-auto-start", description = "Do not start the bundles", required = false, multiValued = false) + boolean noStart; + + @Option(name = "-m", aliases = "--no-auto-manage", description = "Do not automatically manage bundles", required = false, multiValued = false) + boolean noManage; + + @Option(name = "-v", aliases = "--verbose", description = "Explain what is being done") + boolean verbose; + + @Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only") + boolean simulate; + + @Option(name = "-g", aliases = "--region", description = "Region to install to") + String region; + + @Override + public Object execute() throws Exception { + EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class); + if (simulate) { + options.add(FeaturesService.Option.Simulate); + } + if (noStart) { + options.add(FeaturesService.Option.NoAutoStartBundles); + } + if (noRefresh) { + options.add(FeaturesService.Option.NoAutoRefreshBundles); + } + if (noManage) { + options.add(FeaturesService.Option.NoAutoManageBundles); + } + if (verbose) { + options.add(FeaturesService.Option.Verbose); + } + Map<String, Set<String>> reqs = new HashMap<>(); + reqs.put(region == null ? FeaturesService.ROOT_REGION : region, new HashSet<>(requirements)); + featuresService.addRequirements(reqs, options); + return null; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java ---------------------------------------------------------------------- diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java b/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java new file mode 100644 index 0000000..195b385 --- /dev/null +++ b/features/command/src/main/java/org/apache/karaf/features/command/RequirementList.java @@ -0,0 +1,59 @@ +/* + * 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.karaf.features.command; + +import java.util.Map; +import java.util.Set; + +import org.apache.karaf.features.FeaturesService; +import org.apache.karaf.shell.api.action.Action; +import org.apache.karaf.shell.api.action.Command; +import org.apache.karaf.shell.api.action.Option; +import org.apache.karaf.shell.api.action.lifecycle.Reference; +import org.apache.karaf.shell.api.action.lifecycle.Service; +import org.apache.karaf.shell.support.table.ShellTable; + +@Command(scope = "feature", name = "requirement-list", description = "List provisioning requirements.") +@Service +public class RequirementList implements Action { + + @Reference + private FeaturesService featuresService; + + @Option(name = "--no-format", description = "Disable table rendered output") + boolean noFormat; + + @Override + public Object execute() throws Exception { + Map<String, Set<String>> requirements = featuresService.listRequirements(); + + ShellTable table = new ShellTable(); + table.column("Region"); + table.column("Requirement"); + table.emptyTableText("No requirements defined"); + + for (Map.Entry<String, Set<String>> entry : requirements.entrySet()) { + for (String requirement : entry.getValue()) { + table.addRow().addContent(entry.getKey(), requirement); + } + } + + table.print(System.out, !noFormat); + + return null; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java ---------------------------------------------------------------------- diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java b/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java new file mode 100644 index 0000000..a2385e9 --- /dev/null +++ b/features/command/src/main/java/org/apache/karaf/features/command/RequirementRemove.java @@ -0,0 +1,85 @@ +/* + * 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.karaf.features.command; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.karaf.features.FeaturesService; +import org.apache.karaf.shell.api.action.Action; +import org.apache.karaf.shell.api.action.Argument; +import org.apache.karaf.shell.api.action.Command; +import org.apache.karaf.shell.api.action.Option; +import org.apache.karaf.shell.api.action.lifecycle.Reference; +import org.apache.karaf.shell.api.action.lifecycle.Service; + +@Command(scope = "feature", name = "requirement-remove", description = "Remove provisioning requirements.") +@Service +public class RequirementRemove implements Action { + + @Reference + private FeaturesService featuresService; + + @Argument(required = true, multiValued = true) + List<String> requirements; + + @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false) + boolean noRefresh; + + @Option(name = "-s", aliases = "--no-auto-start", description = "Do not start the bundles", required = false, multiValued = false) + boolean noStart; + + @Option(name = "-m", aliases = "--no-auto-manage", description = "Do not automatically manage bundles", required = false, multiValued = false) + boolean noManage; + + @Option(name = "-v", aliases = "--verbose", description = "Explain what is being done") + boolean verbose; + + @Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only") + boolean simulate; + + @Option(name = "-g", aliases = "--region", description = "Region to install to") + String region; + + @Override + public Object execute() throws Exception { + EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class); + if (simulate) { + options.add(FeaturesService.Option.Simulate); + } + if (noStart) { + options.add(FeaturesService.Option.NoAutoStartBundles); + } + if (noRefresh) { + options.add(FeaturesService.Option.NoAutoRefreshBundles); + } + if (noManage) { + options.add(FeaturesService.Option.NoAutoManageBundles); + } + if (verbose) { + options.add(FeaturesService.Option.Verbose); + } + Map<String, Set<String>> reqs = new HashMap<>(); + reqs.put(region == null ? FeaturesService.ROOT_REGION : region, new HashSet<>(requirements)); + featuresService.removeRequirements(reqs, options); + return null; + } +} http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/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 e266402..8f40a6d 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 @@ -93,6 +93,8 @@ public interface FeaturesService { void installFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception; + void addRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception; + void uninstallFeature(String name, EnumSet<Option> options) throws Exception; void uninstallFeature(String name) throws Exception; @@ -105,6 +107,8 @@ public interface FeaturesService { void uninstallFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception; + void removeRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception; + void updateFeaturesState(Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) throws Exception; Feature[] listFeatures() throws Exception; @@ -113,6 +117,8 @@ public interface FeaturesService { Feature[] listInstalledFeatures() throws Exception; + Map<String, Set<String>> listRequirements(); + boolean isRequired(Feature f); boolean isInstalled(Feature f); http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java index f64bc0f..30a789a 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/Subsystem.java @@ -27,6 +27,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.felix.resolver.Util; +import org.apache.felix.utils.manifest.Clause; +import org.apache.felix.utils.manifest.Parser; import org.apache.felix.utils.version.VersionRange; import org.apache.felix.utils.version.VersionTable; import org.apache.karaf.features.BundleInfo; @@ -46,7 +48,6 @@ import org.apache.karaf.features.internal.resolver.ResourceUtils; import org.apache.karaf.features.internal.service.Overrides; import org.osgi.framework.BundleException; import org.osgi.framework.Version; -import org.osgi.resource.Capability; import org.osgi.resource.Requirement; import org.osgi.resource.Resource; @@ -54,6 +55,7 @@ import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_FEA import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM; import static org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement; import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri; +import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureRequirement; import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet; import static org.eclipse.equinox.region.RegionFilter.VISIBLE_ALL_NAMESPACE; import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE; @@ -92,6 +94,8 @@ public class Subsystem extends ResourceImpl { private final List<Resource> installable = new ArrayList<>(); private final Map<String, DependencyInfo> dependencies = new HashMap<>(); + private final List<String> bundles = new ArrayList<>(); + public Subsystem(String name) { super(name, TYPE_SUBSYSTEM, Version.emptyVersion); this.name = name; @@ -183,14 +187,16 @@ public class Subsystem extends ResourceImpl { throw new UnsupportedOperationException("Can not create application subsystems inside a feature subsystem"); } // Create subsystem - Subsystem as = new Subsystem(getName() + "/" + name, this, acceptDependencies); + String childName = getName() + "/" + name; + Subsystem as = new Subsystem(childName, this, acceptDependencies); children.add(as); // Add a requirement to force its resolution - Capability identity = as.getCapabilities(IDENTITY_NAMESPACE).iterator().next(); - Object bsn = identity.getAttributes().get(IDENTITY_NAMESPACE); + Map<String, Object> attrs = new HashMap<>(); + attrs.put(IDENTITY_NAMESPACE, childName); + attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_SUBSYSTEM); Requirement requirement = new RequirementImpl(this, IDENTITY_NAMESPACE, Collections.<String, String>emptyMap(), - Collections.singletonMap(IDENTITY_NAMESPACE, bsn)); + attrs); addRequirement(requirement); // Add it to repo installable.add(as); @@ -205,6 +211,39 @@ public class Subsystem extends ResourceImpl { ResourceUtils.addIdentityRequirement(this, name, TYPE_FEATURE, range); } + public void require(String requirement) throws BundleException { + int idx = requirement.indexOf(":"); + String type, req; + if (idx >= 0) { + type = requirement.substring(0, idx); + req = requirement.substring(idx + 1); + } else { + type = "feature"; + req = requirement; + } + switch (type) { + case "feature": + addRequirement(toFeatureRequirement(req)); + break; + case "requirement": + addRequirement(req); + break; + case "bundle": + bundles.add(req); + break; + } + } + + protected void addRequirement(String requirement) throws BundleException { + for (Requirement req : ResourceBuilder.parseRequirement(this, requirement)) { + Object range = req.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE); + if (range instanceof String) { + req.getAttributes().put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((String) range)); + } + addRequirement(req); + } + } + public Map<String, BundleInfo> getBundleInfos() { Map<String, BundleInfo> infos = new HashMap<>(); for (DependencyInfo di : dependencies.values()) { @@ -286,10 +325,10 @@ public class Subsystem extends ResourceImpl { for (Subsystem child : children) { child.downloadBundles(manager, overrides, featureResolutionRange); } + final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>(); + final Downloader downloader = manager.createDownloader(); + final Map<BundleInfo, Conditional> infos = new HashMap<>(); if (feature != null) { - final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>(); - final Downloader downloader = manager.createDownloader(); - final Map<BundleInfo, Conditional> infos = new HashMap<>(); for (Conditional cond : feature.getConditional()) { for (final BundleInfo bi : cond.getBundles()) { infos.put(bi, cond); @@ -298,29 +337,41 @@ public class Subsystem extends ResourceImpl { for (BundleInfo bi : feature.getBundles()) { infos.put(bi, null); } - for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) { - final BundleInfo bi = entry.getKey(); - final String loc = bi.getLocation(); - downloader.download(loc, new DownloadCallback() { - @Override - public void downloaded(StreamProvider provider) throws Exception { - ResourceImpl res = createResource(loc, provider.getMetadata()); - bundles.put(loc, res); - } - }); - } - for (String override : overrides) { - final String loc = Overrides.extractUrl(override); - downloader.download(loc, new DownloadCallback() { - @Override - public void downloaded(StreamProvider provider) throws Exception { - ResourceImpl res = createResource(loc, provider.getMetadata()); - bundles.put(loc, res); - } - }); - } - downloader.await(); - Overrides.override(bundles, overrides); + } + for (Map.Entry<BundleInfo, Conditional> entry : infos.entrySet()) { + final BundleInfo bi = entry.getKey(); + final String loc = bi.getLocation(); + downloader.download(loc, new DownloadCallback() { + @Override + public void downloaded(StreamProvider provider) throws Exception { + ResourceImpl res = createResource(loc, provider.getMetadata()); + bundles.put(loc, res); + } + }); + } + for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) { + final String loc = bundle.getName(); + downloader.download(loc, new DownloadCallback() { + @Override + public void downloaded(StreamProvider provider) throws Exception { + ResourceImpl res = createResource(loc, provider.getMetadata()); + bundles.put(loc, res); + } + }); + } + for (String override : overrides) { + final String loc = Overrides.extractUrl(override); + downloader.download(loc, new DownloadCallback() { + @Override + public void downloaded(StreamProvider provider) throws Exception { + ResourceImpl res = createResource(loc, provider.getMetadata()); + bundles.put(loc, res); + } + }); + } + downloader.await(); + Overrides.override(bundles, overrides); + if (feature != null) { // Add conditionals Map<Conditional, Resource> resConds = new HashMap<>(); for (Conditional cond : feature.getConditional()) { @@ -350,6 +401,23 @@ public class Subsystem extends ResourceImpl { } } } + for (Clause bundle : Parser.parseClauses(this.bundles.toArray(new String[this.bundles.size()]))) { + final String loc = bundle.getName(); + boolean dependency = Boolean.parseBoolean(bundle.getAttribute("dependency")); + boolean start = bundle.getAttribute("start") == null || Boolean.parseBoolean(bundle.getAttribute("start")); + int startLevel = 0; + try { + startLevel = Integer.parseInt(bundle.getAttribute("start-level")); + } catch (NumberFormatException e) { + // Ignore + } + if (dependency) { + addDependency(bundles.get(loc), false, start, startLevel); + } else { + doAddDependency(bundles.get(loc), true, start, startLevel); + addIdentityRequirement(this, bundles.get(loc)); + } + } // Compute dependencies for (DependencyInfo info : dependencies.values()) { installable.add(info.resource); http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java index f8f7bca..5a92494 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/SubsystemResolver.java @@ -84,18 +84,17 @@ public class SubsystemResolver { private RegionDigraph flatDigraph; private Map<String, Map<String, BundleInfo>> bundleInfos; - public SubsystemResolver(DownloadManager manager) { this.manager = manager; } public void prepare( Collection<Feature> allFeatures, - Map<String, Set<String>> features, + Map<String, Set<String>> requirements, Map<String, Set<BundleRevision>> system ) throws Exception { // Build subsystems on the fly - for (Map.Entry<String, Set<String>> entry : features.entrySet()) { + for (Map.Entry<String, Set<String>> entry : requirements.entrySet()) { String[] parts = entry.getKey().split("/"); if (root == null) { root = new Subsystem(parts[0]); @@ -106,18 +105,8 @@ public class SubsystemResolver { for (int i = 1; i < parts.length; i++) { ss = getOrCreateChild(ss, parts[i]); } - for (String feature : entry.getValue()) { - String name; - String range; - int idx = feature.indexOf('/'); - if (idx >= 0) { - name = feature.substring(0, idx); - range = feature.substring(idx + 1); - } else { - name = feature; - range = null; - } - ss.requireFeature(name, range); + for (String requirement : entry.getValue()) { + ss.require(requirement); } } if (root == null) { http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java index d3f5527..210c375 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Map; import org.apache.felix.utils.version.VersionRange; +import org.apache.felix.utils.version.VersionTable; +import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.osgi.resource.Capability; import org.osgi.resource.Resource; @@ -115,4 +117,28 @@ public final class ResourceUtils { } } + public static String toFeatureRequirement(String feature) { + String[] parts = feature.split("/"); + Map<String, Object> attrs = new HashMap<>(); + attrs.put(IDENTITY_NAMESPACE, parts[0]); + attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE); + if (parts.length > 1) { + attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(parts[1])); + } + Map<String, String> dirs = new HashMap<>(); + dirs.put(Constants.FILTER_DIRECTIVE, SimpleFilter.convert(attrs).toString()); + return new RequirementImpl(null, IDENTITY_NAMESPACE, dirs, attrs).toString(); + } + + public static String toFeatureCapability(String feature) { + String[] parts = feature.split("/"); + Map<String, String> dirs = new HashMap<>(); + Map<String, Object> attrs = new HashMap<>(); + attrs.put(IDENTITY_NAMESPACE, parts[0]); + attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE); + if (parts.length > 1) { + attrs.put(CAPABILITY_VERSION_ATTRIBUTE, VersionTable.getVersion(parts[1])); + } + return new CapabilityImpl(null, IDENTITY_NAMESPACE, dirs, attrs).toString(); + } } http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java index d80fe3d..4d289cb 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java @@ -148,7 +148,7 @@ public class Deployer { String updateSnaphots; Repository globalRepository; - Map<String, Set<String>> requestedFeatures; + Map<String, Set<String>> requirements; Map<String, Map<String, FeaturesService.RequestedState>> stateChanges; EnumSet<FeaturesService.Option> options; } @@ -199,13 +199,10 @@ public class Deployer { map(dstate.bundles)); // Resolve - // TODO: requirements - // TODO: bundles - SubsystemResolver resolver = new SubsystemResolver(manager); resolver.prepare( dstate.features.values(), - request.requestedFeatures, + request.requirements, apply(unmanagedBundles, adapt(BundleRevision.class)) ); Set<String> prereqs = resolver.collectPrerequisites(); @@ -242,9 +239,9 @@ public class Deployer { newRequest.globalRepository = request.globalRepository; newRequest.options = request.options; newRequest.overrides = request.overrides; - newRequest.requestedFeatures = copy(dstate.state.requestedFeatures); + newRequest.requirements = copy(dstate.state.requirements); for (String prereq : prereqs) { - addToMapSet(newRequest.requestedFeatures, ROOT_REGION, prereq); + addToMapSet(newRequest.requirements, ROOT_REGION, prereq); } newRequest.stateChanges = Collections.emptyMap(); newRequest.updateSnaphots = request.updateSnaphots; @@ -671,6 +668,9 @@ public class Deployer { callback.setBundleStartLevel(bundle, startLevel); } FeaturesService.RequestedState reqState = states.get(resource); + if (reqState == null) { + reqState = FeaturesService.RequestedState.Started; + } switch (reqState) { case Started: toResolve.add(bundle); @@ -689,7 +689,7 @@ public class Deployer { // State newState = new State(); newState.bundleChecksums.putAll(deployment.bundleChecksums); - newState.requestedFeatures.putAll(request.requestedFeatures); + newState.requirements.putAll(request.requirements); newState.installedFeatures.putAll(installedFeatures); newState.stateFeatures.putAll(stateFeatures); newState.managedBundles.putAll(managedBundles); http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/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 03686aa..ce01342 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 @@ -69,8 +69,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.karaf.features.internal.service.StateStorage.toStringStringSetMap; +import static org.apache.karaf.features.internal.util.MapUtils.add; import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet; import static org.apache.karaf.features.internal.util.MapUtils.copy; +import static org.apache.karaf.features.internal.util.MapUtils.remove; /** * @@ -164,6 +166,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall } + @SuppressWarnings("unchecked") private void checkResolve() { if (bundle == null) { return; // Most certainly in unit tests @@ -190,7 +193,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall // Resolve try { Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap(); - doInstallFeaturesInThread(requestedFeatures, stateChanges, copyState(), options); + doProvisionInThread(requestedFeatures, stateChanges, copyState(), options); } catch (Exception e) { LOGGER.warn("Error updating state", e); } @@ -206,7 +209,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall request.put("features", requestedFeatures); request.put("options", opts); try ( - FileOutputStream fos = new FileOutputStream(resolveFile); + FileOutputStream fos = new FileOutputStream(resolveFile) ) { JsonWriter.write(fos, request); } @@ -599,9 +602,9 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall @Override public boolean isRequired(Feature f) { - String id = f.getName() + "/" + new VersionRange(f.getVersion(), true); + String id = "feature:" + f.getName() + "/" + new VersionRange(f.getVersion(), true); synchronized (lock) { - Set<String> features = state.requestedFeatures.get(ROOT_REGION); + Set<String> features = state.requirements.get(ROOT_REGION); return features != null && features.contains(id); } } @@ -678,7 +681,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall @Override public void installFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception { State state = copyState(); - Map<String, Set<String>> required = copy(state.requestedFeatures); + Map<String, Set<String>> required = copy(state.requirements); if (region == null || region.isEmpty()) { region = ROOT_REGION; } @@ -713,14 +716,16 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall fl = new HashSet<>(); required.put(region, fl); } - fl.addAll(featuresToAdd); + for (String feature : featuresToAdd) { + fl.add("feature:" + feature); + } Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap(); - doInstallFeaturesInThread(required, stateChanges, state, options); + doProvisionInThread(required, stateChanges, state, options); } public void uninstallFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception { State state = copyState(); - Map<String, Set<String>> required = copy(state.requestedFeatures); + Map<String, Set<String>> required = copy(state.requirements); if (region == null || region.isEmpty()) { region = ROOT_REGION; } @@ -734,7 +739,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall List<String> toRemove = new ArrayList<>(); feature = normalize(feature); if (feature.endsWith("/0.0.0")) { - String nameSep = feature.substring(0, feature.indexOf("/") + 1); + String nameSep = "feature:" + feature.substring(0, feature.indexOf("/") + 1); for (String f : fl) { if (normalize(f).startsWith(nameSep)) { toRemove.add(f); @@ -743,7 +748,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall } else { String name = feature.substring(0, feature.indexOf("/")); String version = feature.substring(feature.indexOf("/") + 1); - String req = name + "/" + new VersionRange(version, true); + String req = "feature:" + name + "/" + new VersionRange(version, true); toRemove.add(req); } toRemove.retainAll(fl); @@ -757,7 +762,9 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall if (i > 0) { sb.append(", "); } - sb.append(toRemove.get(i)); + String f = toRemove.get(i); + String version = f.substring(f.indexOf("/") + 1); + sb.append(version); } sb.append("). Please specify the version to uninstall."); throw new IllegalArgumentException(sb.toString()); @@ -779,13 +786,38 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall required.remove(region); } Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap(); - doInstallFeaturesInThread(required, stateChanges, state, options); + doProvisionInThread(required, stateChanges, state, options); } @Override public void updateFeaturesState(Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) throws Exception { State state = copyState(); - doInstallFeaturesInThread(copy(state.requestedFeatures), stateChanges, state, options); + doProvisionInThread(copy(state.requirements), stateChanges, state, options); + } + + @Override + public void addRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception { + State state = copyState(); + Map<String, Set<String>> required = copy(state.requirements); + add(required, requirements); + Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap(); + doProvisionInThread(required, stateChanges, state, options); + } + + @Override + public void removeRequirements(Map<String, Set<String>> requirements, EnumSet<Option> options) throws Exception { + State state = copyState(); + Map<String, Set<String>> required = copy(state.requirements); + remove(required, requirements); + Map<String, Map<String, RequestedState>> stateChanges = Collections.emptyMap(); + doProvisionInThread(required, stateChanges, state, options); + } + + @Override + public Map<String, Set<String>> listRequirements() { + synchronized (lock) { + return copy(this.state.requirements); + } } private State copyState() { @@ -810,16 +842,16 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall * the command may be interrupted while waiting for the refresh to be done, leading * to bundles not being started after the refresh. */ - public void doInstallFeaturesInThread(final Map<String, Set<String>> features, - final Map<String, Map<String, RequestedState>> stateChanges, - final State state, - final EnumSet<Option> options) throws Exception { + public void doProvisionInThread(final Map<String, Set<String>> requirements, + final Map<String, Map<String, RequestedState>> stateChanges, + final State state, + final EnumSet<Option> options) throws Exception { ExecutorService executor = Executors.newCachedThreadPool(); try { executor.submit(new Callable<Object>() { @Override public Object call() throws Exception { - doInstallFeatures(features, stateChanges, state, options); + doProvision(requirements, stateChanges, state, options); return null; } }).get(); @@ -888,13 +920,13 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall return dstate; } - private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requestedFeatures, Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) { + private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requirements, Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) { Deployer.DeploymentRequest request = new Deployer.DeploymentRequest(); request.bundleUpdateRange = bundleUpdateRange; request.featureResolutionRange = featureResolutionRange; request.globalRepository = globalRepository; request.overrides = Overrides.loadOverrides(overrides); - request.requestedFeatures = requestedFeatures; + request.requirements = requirements; request.stateChanges = stateChanges; request.options = options; return request; @@ -902,17 +934,17 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall - public void doInstallFeatures(Map<String, Set<String>> requestedFeatures, // all request features - Map<String, Map<String, RequestedState>> stateChanges, // features state changes - State state, // current state - EnumSet<Option> options // installation options + public void doProvision(Map<String, Set<String>> requirements, // all requirements + Map<String, Map<String, RequestedState>> stateChanges, // features state changes + State state, // current state + EnumSet<Option> options // installation options ) throws Exception { Set<String> prereqs = new HashSet<>(); while (true) { try { Deployer.DeploymentState dstate = getDeploymentState(state); - Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options); + Deployer.DeploymentRequest request = getDeploymentRequest(requirements, stateChanges, options); new Deployer(new SimpleDownloader(), this).deploy(dstate, request); break; } catch (Deployer.PartialDeploymentException e) { @@ -961,7 +993,7 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall @Override public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException { - writeResolve(request.requestedFeatures, request.options); + writeResolve(request.requirements, request.options); } @Override http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java index b1a3ccd..a059b0b 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java @@ -28,7 +28,7 @@ public class State { public final AtomicBoolean bootDone = new AtomicBoolean(); public final Set<String> repositories = new TreeSet<>(); - public final Map<String, Set<String>> requestedFeatures = new HashMap<>(); + public final Map<String, Set<String>> requirements = new HashMap<>(); public final Map<String, Set<String>> installedFeatures = new HashMap<>(); public final Map<String, Map<String, String>> stateFeatures = new HashMap<>(); public final Map<String, Set<Long>> managedBundles = new HashMap<>(); @@ -47,7 +47,7 @@ public class State { private static void copy(State from, State to, boolean clear) { if (clear) { to.repositories.clear(); - to.requestedFeatures.clear(); + to.requirements.clear(); to.installedFeatures.clear(); to.stateFeatures.clear(); to.managedBundles.clear(); @@ -55,7 +55,7 @@ public class State { } to.bootDone.set(from.bootDone.get()); MapUtils.copy(from.repositories, to.repositories); - MapUtils.copy(from.requestedFeatures, to.requestedFeatures); + MapUtils.copy(from.requirements, to.requirements); MapUtils.copy(from.installedFeatures, to.installedFeatures); MapUtils.copy(from.stateFeatures, to.stateFeatures); MapUtils.copy(from.managedBundles, to.managedBundles); http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java index e085b65..20e0fcd 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/StateStorage.java @@ -32,7 +32,7 @@ public abstract class StateStorage { public void load(State state) throws IOException { state.repositories.clear(); - state.requestedFeatures.clear(); + state.requirements.clear(); state.installedFeatures.clear(); state.managedBundles.clear(); try ( @@ -42,7 +42,7 @@ public abstract class StateStorage { Map json = (Map) JsonReader.read(is); state.bootDone.set((Boolean) json.get("bootDone")); state.repositories.addAll(toStringSet((Collection) json.get("repositories"))); - state.requestedFeatures.putAll(toStringStringSetMap((Map) json.get("features"))); + state.requirements.putAll(toStringStringSetMap((Map) json.get("features"))); state.installedFeatures.putAll(toStringStringSetMap((Map) json.get("installed"))); state.stateFeatures.putAll(toStringStringStringMapMap((Map) json.get("state"))); state.managedBundles.putAll(toStringLongSetMap((Map) json.get("managed"))); @@ -59,7 +59,7 @@ public abstract class StateStorage { Map<String, Object> json = new HashMap<>(); json.put("bootDone", state.bootDone.get()); json.put("repositories", state.repositories); - json.put("features", state.requestedFeatures); + json.put("features", state.requirements); json.put("installed", state.installedFeatures); json.put("state", state.stateFeatures); json.put("managed", state.managedBundles); http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java ---------------------------------------------------------------------- diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java index e6b5b8f..11f9b0a 100644 --- a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java +++ b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java @@ -35,6 +35,8 @@ import org.osgi.resource.Capability; import org.osgi.resource.Resource; import org.osgi.resource.Wire; +import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureCapability; +import static org.apache.karaf.features.internal.resolver.ResourceUtils.toFeatureRequirement; import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet; import static org.junit.Assert.assertEquals; @@ -158,6 +160,28 @@ public class SubsystemTest { verify(resolver, expected); } + @Test + public void testBundle() throws Exception { + RepositoryImpl repo = new RepositoryImpl(getClass().getResource("data1/features.xml").toURI()); + + Map<String, Set<String>> features = new HashMap<String, Set<String>>(); + addToMapSet(features, "root/apps1", "bundle:a"); + addToMapSet(features, "root/apps1", "bundle:c;dependency=true"); + Map<String, Set<String>> expected = new HashMap<String, Set<String>>(); + addToMapSet(expected, "root/apps1", "a/1.0.0"); + addToMapSet(expected, "root/apps1", "c/1.0.0"); + + SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data1")); + resolver.prepare(Arrays.asList(repo.getFeatures()), + features, + Collections.<String, Set<BundleRevision>>emptyMap()); + resolver.resolve(Collections.<String>emptySet(), + FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, + null); + + verify(resolver, expected); + } + private void verify(SubsystemResolver resolver, Map<String, Set<String>> expected) { Map<String, Set<String>> mapping = getBundleNamesPerRegions(resolver); if (!expected.equals(mapping)) { http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java ---------------------------------------------------------------------- diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java index 723df3b..9478b67 100644 --- a/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java +++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/DeployerTest.java @@ -22,7 +22,6 @@ import java.net.URL; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; @@ -101,8 +100,8 @@ public class DeployerTest { request.overrides = Collections.emptySet(); request.stateChanges = Collections.emptyMap(); request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; - request.requestedFeatures = new HashMap<>(); - addToMapSet(request.requestedFeatures, ROOT_REGION, f100.getName() + "/" + new VersionRange(f100.getVersion(), true)); + request.requirements = new HashMap<>(); + addToMapSet(request.requirements, ROOT_REGION, f100.getName() + "/" + new VersionRange(f100.getVersion(), true)); deployer.deploy(dstate, request); @@ -194,8 +193,8 @@ public class DeployerTest { request.overrides = Collections.emptySet(); request.stateChanges = Collections.emptyMap(); request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; - request.requestedFeatures = new HashMap<>(); - addToMapSet(request.requestedFeatures, ROOT_REGION, f101.getName() + "/" + new VersionRange(f101.getVersion(), true)); + request.requirements = new HashMap<>(); + addToMapSet(request.requirements, ROOT_REGION, f101.getName() + "/" + new VersionRange(f101.getVersion(), true)); deployer.deploy(dstate, request); @@ -252,8 +251,8 @@ public class DeployerTest { request.overrides = Collections.emptySet(); request.stateChanges = Collections.emptyMap(); request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; - request.requestedFeatures = new HashMap<>(); - addToMapSet(request.requestedFeatures, ROOT_REGION, f1.getName()); + request.requirements = new HashMap<>(); + addToMapSet(request.requirements, ROOT_REGION, f1.getName()); deployer.deploy(dstate, request); @@ -314,8 +313,8 @@ public class DeployerTest { request.overrides = Collections.emptySet(); request.stateChanges = Collections.emptyMap(); request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; - request.requestedFeatures = new HashMap<>(); - addToMapSet(request.requestedFeatures, ROOT_REGION, f2.getName()); + request.requirements = new HashMap<>(); + addToMapSet(request.requirements, ROOT_REGION, f2.getName()); try { deployer.deploy(dstate, request); @@ -369,8 +368,8 @@ public class DeployerTest { request.overrides = Collections.emptySet(); request.stateChanges = Collections.emptyMap(); request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; - request.requestedFeatures = new HashMap<>(); - addToMapSet(request.requestedFeatures, ROOT_REGION, f2.getName()); + request.requirements = new HashMap<>(); + addToMapSet(request.requirements, ROOT_REGION, f2.getName()); deployer.deploy(dstate, request); http://git-wip-us.apache.org/repos/asf/karaf/blob/98123912/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java ---------------------------------------------------------------------- diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java index 857936c..fbd819f 100644 --- a/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java +++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/StateStorageTest.java @@ -37,7 +37,7 @@ public class StateStorageTest { State oldState = new State(); oldState.bootDone.set(true); oldState.bundleChecksums.put(4l, 32794l); - oldState.requestedFeatures.put("bar", Collections.singleton("f1")); + oldState.requirements.put("bar", Collections.singleton("f1")); oldState.managedBundles.put("reg", Collections.singleton(32l)); oldState.managedBundles.put("reg2", new HashSet<Long>(Arrays.asList(24l, 43l))); oldState.repositories.add("repo"); @@ -53,7 +53,7 @@ public class StateStorageTest { assertEquals(oldState.bootDone.get(), newState.bootDone.get()); assertEquals(oldState.bundleChecksums, newState.bundleChecksums); - assertEquals(oldState.requestedFeatures, newState.requestedFeatures); + assertEquals(oldState.requirements, newState.requirements); assertEquals(oldState.managedBundles, newState.managedBundles); assertEquals(oldState.repositories, newState.repositories); }
