Repository: karaf Updated Branches: refs/heads/master b46bd22bc -> cde64ae2a
[KARAF-2988] Add support for prerequisites on features Project: http://git-wip-us.apache.org/repos/asf/karaf/repo Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/cde64ae2 Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/cde64ae2 Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/cde64ae2 Branch: refs/heads/master Commit: cde64ae2ab110c4659bc23255c7c2dc1d3eb03f0 Parents: c1a6ca7 Author: Guillaume Nodet <gno...@gmail.com> Authored: Mon May 19 21:14:50 2014 +0200 Committer: Guillaume Nodet <gno...@gmail.com> Committed: Mon May 19 21:16:05 2014 +0200 ---------------------------------------------------------------------- assemblies/apache-karaf/pom.xml | 1 + .../enterprise/src/main/feature/feature.xml | 2 + assemblies/features/framework/pom.xml | 23 ---- .../framework/src/main/feature/feature.xml | 1 - .../etc/org.apache.karaf.features.repos.cfg | 1 + .../standard/src/main/feature/feature.xml | 9 +- .../org/apache/karaf/features/Dependency.java | 2 + .../features/internal/model/Dependency.java | 23 +++- .../karaf/features/internal/model/Feature.java | 13 +- .../features/internal/region/Subsystem.java | 76 ++++++++---- .../internal/region/SubsystemResolver.java | 38 +++--- .../features/internal/service/Deployer.java | 64 +++++++++- .../internal/service/FeaturesServiceImpl.java | 19 ++- .../karaf/features/karaf-features-1.3.0.xsd | 2 + .../apache/karaf/features/RepositoryTest.java | 4 +- .../features/internal/region/SubsystemTest.java | 42 +++---- .../features/internal/service/DeployerTest.java | 118 +++++++++++++++++++ .../features/internal/service/data2/a100.mf | 5 + .../features/internal/service/data2/b100.mf | 5 + .../internal/service/data2/features.xml | 30 +++++ 20 files changed, 373 insertions(+), 105 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/assemblies/apache-karaf/pom.xml ---------------------------------------------------------------------- diff --git a/assemblies/apache-karaf/pom.xml b/assemblies/apache-karaf/pom.xml index a799b0a..6b721ed 100644 --- a/assemblies/apache-karaf/pom.xml +++ b/assemblies/apache-karaf/pom.xml @@ -160,6 +160,7 @@ <feature>wrapper</feature> </installedFeatures> <bootFeatures> + <feature>wrap</feature> <feature>aries-blueprint</feature> <feature>shell</feature> <feature>shell-compat</feature> http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/assemblies/features/enterprise/src/main/feature/feature.xml ---------------------------------------------------------------------- diff --git a/assemblies/features/enterprise/src/main/feature/feature.xml b/assemblies/features/enterprise/src/main/feature/feature.xml index ad2ecf6..e38335c 100644 --- a/assemblies/features/enterprise/src/main/feature/feature.xml +++ b/assemblies/features/enterprise/src/main/feature/feature.xml @@ -111,6 +111,7 @@ <feature name="hibernate" description="Hibernate 4.2.x JPA persistence engine support" version="${hibernate42.version}"> <details>Enable Hibernate 4.2.x as persistence engine.</details> + <feature prerequisite="true">wrap</feature> <feature>transaction</feature> <feature>http</feature> <bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.antlr/${antlr.bundle.version}</bundle> @@ -137,6 +138,7 @@ <feature name="hibernate" description="Hibernate 4.3.x JPA persistence engine support" version="${hibernate43.version}"> <details>Enable Hibernate 4.3.x as persistence engine.</details> + <feature prerequisite="true">wrap</feature> <feature>transaction</feature> <feature>http</feature> <bundle dependency="true" start-level="30">mvn:org.hibernate.javax.persistence/hibernate-jpa-2.1-api/1.0.0.Final</bundle> http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/assemblies/features/framework/pom.xml ---------------------------------------------------------------------- diff --git a/assemblies/features/framework/pom.xml b/assemblies/features/framework/pom.xml index 51dc83a..3e8ba86 100644 --- a/assemblies/features/framework/pom.xml +++ b/assemblies/features/framework/pom.xml @@ -139,29 +139,6 @@ </exclusion> </exclusions> </dependency> - <dependency> - <groupId>org.ops4j.pax.url</groupId> - <artifactId>pax-url-wrap</artifactId> - <classifier>uber</classifier> - <exclusions> - <exclusion> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </exclusion> - <exclusion> - <groupId>org.ops4j.base</groupId> - <artifactId>ops4j-base-net</artifactId> - </exclusion> - <exclusion> - <groupId>org.ops4j.pax.swissbox</groupId> - <artifactId>pax-swissbox-bnd</artifactId> - </exclusion> - <exclusion> - <groupId>org.ops4j.pax.url</groupId> - <artifactId>pax-url-commons</artifactId> - </exclusion> - </exclusions> - </dependency> <dependency> <groupId>org.apache.felix</groupId> http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/assemblies/features/framework/src/main/feature/feature.xml ---------------------------------------------------------------------- diff --git a/assemblies/features/framework/src/main/feature/feature.xml b/assemblies/features/framework/src/main/feature/feature.xml index 51b97e5..3b79d62 100644 --- a/assemblies/features/framework/src/main/feature/feature.xml +++ b/assemblies/features/framework/src/main/feature/feature.xml @@ -24,7 +24,6 @@ <feature version="${project.version}" description="Karaf core feature" name="framework"> <!-- mvn: and wrap: url handlers --> <bundle start="true" start-level="5">mvn:org.ops4j.pax.url/pax-url-aether/${pax.url.version}</bundle> - <bundle start="true" start-level="5">mvn:org.ops4j.pax.url/pax-url-wrap/${pax.url.version}/jar/uber</bundle> <!-- logging --> <bundle start="true" start-level="8">mvn:org.ops4j.pax.logging/pax-logging-api/${pax.logging.version}</bundle> <bundle start="true" start-level="8">mvn:org.ops4j.pax.logging/pax-logging-service/${pax.logging.version}</bundle> http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/assemblies/features/framework/src/main/resources/resources/etc/org.apache.karaf.features.repos.cfg ---------------------------------------------------------------------- diff --git a/assemblies/features/framework/src/main/resources/resources/etc/org.apache.karaf.features.repos.cfg b/assemblies/features/framework/src/main/resources/resources/etc/org.apache.karaf.features.repos.cfg index 7574897..df7ea32 100644 --- a/assemblies/features/framework/src/main/resources/resources/etc/org.apache.karaf.features.repos.cfg +++ b/assemblies/features/framework/src/main/resources/resources/etc/org.apache.karaf.features.repos.cfg @@ -22,6 +22,7 @@ # It could be directly installed using feature:repo-add command # +enterprise = mvn:org.apache.karaf.features/enterprise/${version}/xml/features cellar = mvn:org.apache.karaf.cellar/apache-karaf-cellar/${version}/xml/features camel = mvn:org.apache.camel.karaf/apache-camel/${version}/xml/features camel-extras = mvn:org.apache-extras.camel-extra.karaf/camel-extra/${version}/xml/features http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/assemblies/features/standard/src/main/feature/feature.xml ---------------------------------------------------------------------- diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml index c02dea0..9f68409 100644 --- a/assemblies/features/standard/src/main/feature/feature.xml +++ b/assemblies/features/standard/src/main/feature/feature.xml @@ -84,9 +84,12 @@ </feature> <feature name="deployer" description="Karaf Deployer" version="${project.version}"> - <bundle start="true" start-level="24">mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.wrap/${project.version}</bundle> <bundle start="true" start-level="26">mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.features/${project.version}</bundle> <conditional> + <condition>wrap</condition> + <bundle start="true" start-level="24">mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.wrap/${project.version}</bundle> + </conditional> + <conditional> <condition>req:osgi.extender;filter:="(&(osgi.extender=osgi.blueprint)(version>=1.0))"</condition> <bundle start="true" start-level="24">mvn:org.apache.karaf.deployer/org.apache.karaf.deployer.blueprint/${project.version}</bundle> </conditional> @@ -295,4 +298,8 @@ <bundle>mvn:org.apache.aries.blueprint/org.apache.aries.blueprint.webosgi/${aries.blueprint.web.version}</bundle> </feature> + <feature name="wrap" description="Wrap URL handler"> + <bundle start="true" start-level="5">mvn:org.ops4j.pax.url/pax-url-wrap/${pax.url.version}/jar/uber</bundle> + </feature> + </features> http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/main/java/org/apache/karaf/features/Dependency.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/Dependency.java b/features/core/src/main/java/org/apache/karaf/features/Dependency.java index 5e019c7..421df19 100644 --- a/features/core/src/main/java/org/apache/karaf/features/Dependency.java +++ b/features/core/src/main/java/org/apache/karaf/features/Dependency.java @@ -23,4 +23,6 @@ public interface Dependency { String getVersion(); + boolean isPrerequisite(); + } http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java index b099f28..2a4d8dd 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Dependency.java @@ -42,13 +42,15 @@ import javax.xml.bind.annotation.XmlValue; * </pre> */ @XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "dependency", propOrder = {"value"}) +@XmlType(name = "dependency", propOrder = {"name"}) public class Dependency implements org.apache.karaf.features.Dependency { @XmlValue - protected String value; + protected String name; @XmlAttribute protected String version; + @XmlAttribute + protected boolean prerequisite; /** * Feature name should be non empty string. @@ -57,7 +59,7 @@ public class Dependency implements org.apache.karaf.features.Dependency { * {@link String } */ public String getName() { - return value; + return name; } /** @@ -67,7 +69,7 @@ public class Dependency implements org.apache.karaf.features.Dependency { * {@link String } */ public void setName(String value) { - this.value = value; + this.name = value; } /** @@ -78,7 +80,7 @@ public class Dependency implements org.apache.karaf.features.Dependency { */ public String getVersion() { if (version == null) { - return "0.0.0"; + return Feature.DEFAULT_VERSION; } else { return version; } @@ -94,8 +96,17 @@ public class Dependency implements org.apache.karaf.features.Dependency { this.version = value; } + @Override + public boolean isPrerequisite() { + return prerequisite; + } + + public void setPrerequisite(boolean prerequisite) { + this.prerequisite = prerequisite; + } + public String toString() { - return getName() + Feature.SPLIT_FOR_NAME_AND_VERSION + getVersion(); + return getName() + Feature.VERSION_SEPARATOR + getVersion(); } } http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java ---------------------------------------------------------------------- diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java index 6cf3820..4d3eb12 100644 --- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java +++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java @@ -30,7 +30,6 @@ import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import org.apache.felix.utils.version.VersionCleaner; -import org.apache.felix.utils.version.VersionTable; /** @@ -78,7 +77,7 @@ import org.apache.felix.utils.version.VersionTable; }) public class Feature extends Content implements org.apache.karaf.features.Feature { - public static final String SPLIT_FOR_NAME_AND_VERSION = "/"; + public static final String VERSION_SEPARATOR = "/"; public static final String DEFAULT_VERSION = "0.0.0"; @@ -114,10 +113,10 @@ public class Feature extends Content implements org.apache.karaf.features.Featur public static org.apache.karaf.features.Feature valueOf(String str) { - if (str.contains(SPLIT_FOR_NAME_AND_VERSION)) { - String strName = str.substring(0, str.indexOf(SPLIT_FOR_NAME_AND_VERSION)); - String strVersion = str.substring(str.indexOf(SPLIT_FOR_NAME_AND_VERSION) - + SPLIT_FOR_NAME_AND_VERSION.length(), str.length()); + if (str.contains(VERSION_SEPARATOR)) { + String strName = str.substring(0, str.indexOf(VERSION_SEPARATOR)); + String strVersion = str.substring(str.indexOf(VERSION_SEPARATOR) + + VERSION_SEPARATOR.length(), str.length()); return new Feature(strName, strVersion); } else { return new Feature(str); @@ -128,7 +127,7 @@ public class Feature extends Content implements org.apache.karaf.features.Featur public String getId() { - return getName() + SPLIT_FOR_NAME_AND_VERSION + getVersion(); + return getName() + VERSION_SEPARATOR + getVersion(); } /** http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/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 bfe170e..f64bc0f 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 @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -85,11 +86,11 @@ public class Subsystem extends ResourceImpl { private final boolean acceptDependencies; private final Subsystem parent; private final Feature feature; - private final List<Subsystem> children = new ArrayList<Subsystem>(); + private final List<Subsystem> children = new ArrayList<>(); private final Map<String, Set<String>> importPolicy; private final Map<String, Set<String>> exportPolicy; - private final List<Resource> installable = new ArrayList<Resource>(); - private final Map<String, DependencyInfo> dependencies = new HashMap<String, DependencyInfo>(); + private final List<Resource> installable = new ArrayList<>(); + private final Map<String, DependencyInfo> dependencies = new HashMap<>(); public Subsystem(String name) { super(name, TYPE_SUBSYSTEM, Version.emptyVersion); @@ -117,8 +118,8 @@ public class Subsystem extends ResourceImpl { this.exportPolicy = SHARE_ALL_POLICY; } - Map<String, String> dirs = new HashMap<String, String>(); - Map<String, Object> attrs = new HashMap<String, Object>(); + Map<String, String> dirs = new HashMap<>(); + Map<String, Object> attrs = new HashMap<>(); attrs.put(IDENTITY_NAMESPACE, feature.getName()); attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE); attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(VersionTable.getVersion(feature.getVersion()), true)); @@ -205,7 +206,7 @@ public class Subsystem extends ResourceImpl { } public Map<String, BundleInfo> getBundleInfos() { - Map<String, BundleInfo> infos = new HashMap<String, BundleInfo>(); + Map<String, BundleInfo> infos = new HashMap<>(); for (DependencyInfo di : dependencies.values()) { infos.put(di.getLocation(), di); } @@ -213,14 +214,20 @@ public class Subsystem extends ResourceImpl { } @SuppressWarnings("InfiniteLoopStatement") - public void preResolve(Collection<Feature> features, - DownloadManager manager, - Set<String> overrides, - String featureResolutionRange) throws Exception { + public void build(Collection<Feature> features) throws Exception { for (Subsystem child : children) { - child.preResolve(features, manager, overrides, featureResolutionRange); + child.build(features); } - List<Requirement> processed = new ArrayList<Requirement>(); + if (feature != null) { + for (Dependency dep : feature.getDependencies()) { + Subsystem ss = this; + while (!ss.isAcceptDependencies()) { + ss = ss.getParent(); + } + ss.requireFeature(dep.getName(), dep.getVersion()); + } + } + List<Requirement> processed = new ArrayList<>(); while (true) { List<Requirement> requirements = getRequirements(IDENTITY_NAMESPACE); requirements.removeAll(processed); @@ -240,7 +247,7 @@ public class Subsystem extends ResourceImpl { Subsystem fs = getChild(ssName); if (fs == null) { fs = new Subsystem(ssName, feature, this); - fs.preResolve(features, manager, overrides, featureResolutionRange); + fs.build(features); installable.add(fs); children.add(fs); } @@ -251,10 +258,38 @@ public class Subsystem extends ResourceImpl { processed.add(requirement); } } + } + + public Set<String> collectPrerequisites() { + Set<String> prereqs = new HashSet<>(); + doCollectPrerequisites(prereqs); + return prereqs; + } + + private void doCollectPrerequisites(Set<String> prereqs) { + for (Subsystem child : children) { + child.doCollectPrerequisites(prereqs); + } + if (feature != null) { + for (Dependency dep : feature.getDependencies()) { + if (dep.isPrerequisite()) { + prereqs.add(dep.toString()); + } + } + } + } + + @SuppressWarnings("InfiniteLoopStatement") + public void downloadBundles(DownloadManager manager, + Set<String> overrides, + String featureResolutionRange) throws Exception { + for (Subsystem child : children) { + child.downloadBundles(manager, overrides, featureResolutionRange); + } if (feature != null) { - final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<String, ResourceImpl>(); + final Map<String, ResourceImpl> bundles = new ConcurrentHashMap<>(); final Downloader downloader = manager.createDownloader(); - final Map<BundleInfo, Conditional> infos = new HashMap<BundleInfo, Conditional>(); + final Map<BundleInfo, Conditional> infos = new HashMap<>(); for (Conditional cond : feature.getConditional()) { for (final BundleInfo bi : cond.getBundles()) { infos.put(bi, cond); @@ -286,15 +321,8 @@ public class Subsystem extends ResourceImpl { } downloader.await(); Overrides.override(bundles, overrides); - for (Dependency dep : feature.getDependencies()) { - Subsystem ss = this; - while (!ss.isAcceptDependencies()) { - ss = ss.getParent(); - } - ss.requireFeature(dep.getName(), dep.getVersion()); - } // Add conditionals - Map<Conditional, Resource> resConds = new HashMap<Conditional, Resource>(); + Map<Conditional, Resource> resConds = new HashMap<>(); for (Conditional cond : feature.getConditional()) { FeatureResource resCond = FeatureResource.build(feature, cond, featureResolutionRange, bundles); addIdentityRequirement(this, resCond, false); @@ -382,7 +410,7 @@ public class Subsystem extends ResourceImpl { } Map<String, Set<String>> createPolicy(List<? extends ScopeFilter> filters) { - Map<String, Set<String>> policy = new HashMap<String, Set<String>>(); + Map<String, Set<String>> policy = new HashMap<>(); for (ScopeFilter filter : filters) { addToMapSet(policy, filter.getNamespace(), filter.getFilter()); } http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/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 2682e58..f8f7bca 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 @@ -32,7 +32,6 @@ import org.apache.karaf.features.Feature; import org.apache.karaf.features.internal.download.DownloadManager; import org.apache.karaf.features.internal.download.Downloader; import org.apache.karaf.features.internal.download.StreamProvider; -import org.apache.karaf.features.internal.download.simple.SimpleDownloader; import org.apache.karaf.features.internal.resolver.CapabilityImpl; import org.apache.karaf.features.internal.resolver.CapabilitySet; import org.apache.karaf.features.internal.resolver.ResourceBuilder; @@ -76,6 +75,7 @@ public class SubsystemResolver { private Map<Resource, List<Wire>> wiring; // Cached computed results + private ResourceImpl environmentResource; private Map<String, String> flatSubsystemsMap; private Map<String, Set<Resource>> bundlesPerRegions; private Map<Resource, String> bundles; @@ -85,21 +85,14 @@ public class SubsystemResolver { private Map<String, Map<String, BundleInfo>> bundleInfos; - public SubsystemResolver() { - this(new SimpleDownloader()); - } - public SubsystemResolver(DownloadManager manager) { this.manager = manager; } - public Map<Resource, List<Wire>> resolve( + public void prepare( Collection<Feature> allFeatures, Map<String, Set<String>> features, - Map<String, Set<BundleRevision>> system, - Set<String> overrides, - String featureResolutionRange, - org.osgi.service.repository.Repository globalRepository + Map<String, Set<BundleRevision>> system ) throws Exception { // Build subsystems on the fly for (Map.Entry<String, Set<String>> entry : features.entrySet()) { @@ -128,13 +121,13 @@ public class SubsystemResolver { } } if (root == null) { - return Collections.emptyMap(); + return; } + // Pre-resolve - root.preResolve(allFeatures, manager, overrides, featureResolutionRange); + root.build(allFeatures); // Add system resources - ResourceImpl environmentResource = null; BundleRevision sysBundleRev = null; boolean hasEeCap = false; for (Map.Entry<String, Set<BundleRevision>> entry : system.entrySet()) { @@ -157,7 +150,7 @@ public class SubsystemResolver { Map<String, String> headers = new DictionaryAsMap<>(res.getBundle().getHeaders()); Resource tmp = ResourceBuilder.build(res.getBundle().getLocation(), headers); for (Capability cap : tmp.getCapabilities(ServiceNamespace.SERVICE_NAMESPACE)) { - dummy.addCapability(new CapabilityImpl(dummy, cap.getNamespace(), cap.getDirectives() ,cap.getAttributes())); + dummy.addCapability(new CapabilityImpl(dummy, cap.getNamespace(), cap.getDirectives(), cap.getAttributes())); } ss.addSystemResource(res); for (Capability cap : res.getCapabilities(null)) { @@ -177,6 +170,23 @@ public class SubsystemResolver { environmentResource.addCapabilities(ResourceBuilder.parseCapability(environmentResource, provideCaps)); root.addSystemResource(environmentResource); } + } + + public Set<String> collectPrerequisites() throws Exception { + return root.collectPrerequisites(); + } + + public Map<Resource, List<Wire>> resolve( + Set<String> overrides, + String featureResolutionRange, + final org.osgi.service.repository.Repository globalRepository + ) throws Exception { + if (root == null) { + return Collections.emptyMap(); + } + + // Download bundles + root.downloadBundles(manager, overrides, featureResolutionRange); // Populate digraph and resolve digraph = new StandardRegionDigraph(null, null); http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/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 5a1cd02..d80fe3d 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 @@ -34,6 +34,7 @@ import java.util.TreeMap; import java.util.TreeSet; import org.apache.felix.utils.version.VersionRange; +import org.apache.felix.utils.version.VersionTable; import org.apache.karaf.features.BundleInfo; import org.apache.karaf.features.Feature; import org.apache.karaf.features.FeatureEvent; @@ -58,7 +59,6 @@ import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; -import org.osgi.resource.Capability; import org.osgi.resource.Resource; import org.osgi.resource.Wire; import org.osgi.service.repository.Repository; @@ -118,6 +118,18 @@ public class Deployer { void replaceDigraph(Map<String, Map<String,Map<String,Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException; } + public static class PartialDeploymentException extends Exception { + private final Set<String> missing; + + public PartialDeploymentException(Set<String> missing) { + this.missing = missing; + } + + public Set<String> getMissing() { + return missing; + } + } + static class DeploymentState { State state; Bundle serviceBundle; @@ -191,10 +203,56 @@ public class Deployer { // TODO: bundles SubsystemResolver resolver = new SubsystemResolver(manager); - resolver.resolve( + resolver.prepare( dstate.features.values(), request.requestedFeatures, - apply(unmanagedBundles, adapt(BundleRevision.class)), + apply(unmanagedBundles, adapt(BundleRevision.class)) + ); + Set<String> prereqs = resolver.collectPrerequisites(); + if (!prereqs.isEmpty()) { + for (Iterator<String> iterator = prereqs.iterator(); iterator.hasNext(); ) { + String prereq = iterator.next(); + String[] parts = prereq.split("/"); + VersionRange range; + if (parts[1].equals("0.0.0")) { + range = VersionRange.ANY_VERSION; + } else if (!parts[1].startsWith("[") && !parts[1].startsWith("(")) { + range = new VersionRange(Macro.transform(request.featureResolutionRange, parts[1])); + } else { + range = new VersionRange(parts[1]); + } + boolean found = false; + for (Set<String> featureSet : dstate.state.installedFeatures.values()) { + for (String feature : featureSet) { + String[] p = feature.split("/"); + found = parts[0].equals(p[0]) && range.contains(VersionTable.getVersion(p[1])); + if (found) break; + } + if (found) break; + } + if (found) { + iterator.remove(); + } + } + } + if (!prereqs.isEmpty()) { + DeploymentRequest newRequest = new DeploymentRequest(); + newRequest.bundleUpdateRange = request.bundleUpdateRange; + newRequest.featureResolutionRange = request.featureResolutionRange; + newRequest.globalRepository = request.globalRepository; + newRequest.options = request.options; + newRequest.overrides = request.overrides; + newRequest.requestedFeatures = copy(dstate.state.requestedFeatures); + for (String prereq : prereqs) { + addToMapSet(newRequest.requestedFeatures, ROOT_REGION, prereq); + } + newRequest.stateChanges = Collections.emptyMap(); + newRequest.updateSnaphots = request.updateSnaphots; + deploy(dstate, newRequest); + throw new PartialDeploymentException(prereqs); + } + + resolver.resolve( request.overrides, request.featureResolutionRange, request.globalRepository); http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/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 f0f0677..03686aa 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 @@ -908,9 +908,22 @@ public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCall EnumSet<Option> options // installation options ) throws Exception { - Deployer.DeploymentState dstate = getDeploymentState(state); - Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options); - new Deployer(new SimpleDownloader(), this).deploy(dstate, request); + Set<String> prereqs = new HashSet<>(); + while (true) { + try { + Deployer.DeploymentState dstate = getDeploymentState(state); + Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options); + new Deployer(new SimpleDownloader(), this).deploy(dstate, request); + break; + } catch (Deployer.PartialDeploymentException e) { + if (!prereqs.containsAll(e.getMissing())) { + prereqs.addAll(e.getMissing()); + state = copyState(); + } else { + throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing()); + } + } + } } public void print(String message, boolean verbose) { http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd ---------------------------------------------------------------------- diff --git a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd index e28f05f..4d6e81d 100644 --- a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd +++ b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.3.0.xsd @@ -165,6 +165,7 @@ Dependency of feature. <xs:simpleContent> <xs:extension base="tns:featureName"> <xs:attribute name="version" type="xs:string" default="0.0.0" /> + <xs:attribute name="prerequisite" type="xs:boolean" default="false"/> </xs:extension> </xs:simpleContent> </xs:complexType> @@ -219,6 +220,7 @@ Additional requirements of this feature. </xs:annotation> <xs:simpleContent> <xs:extension base="xs:string"> + <xs:attribute name="prerequisite" type="xs:boolean" default="false"/> </xs:extension> </xs:simpleContent> </xs:complexType> http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java ---------------------------------------------------------------------- diff --git a/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java b/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java index 164dc79..c8da238 100644 --- a/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java +++ b/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java @@ -56,7 +56,7 @@ public class RepositoryTest extends TestCase { assertEquals(0, features[1].getConfigurations().size()); assertNotNull(features[1].getDependencies()); assertEquals(1, features[1].getDependencies().size()); - assertEquals("f1" + org.apache.karaf.features.internal.model.Feature.SPLIT_FOR_NAME_AND_VERSION + org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION, features[1].getDependencies().get(0).toString()); + assertEquals("f1" + org.apache.karaf.features.internal.model.Feature.VERSION_SEPARATOR + org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION, features[1].getDependencies().get(0).toString()); assertNotNull(features[1].getBundles()); assertEquals(1, features[1].getBundles().size()); assertEquals("b3", features[1].getBundles().get(0).getLocation()); @@ -98,7 +98,7 @@ public class RepositoryTest extends TestCase { assertEquals(0, features[1].getConfigurations().size()); assertNotNull(features[1].getDependencies()); assertEquals(1, features[1].getDependencies().size()); - assertEquals("f1" + org.apache.karaf.features.internal.model.Feature.SPLIT_FOR_NAME_AND_VERSION + org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION, features[1].getDependencies().get(0).toString()); + assertEquals("f1" + org.apache.karaf.features.internal.model.Feature.VERSION_SEPARATOR + org.apache.karaf.features.internal.model.Feature.DEFAULT_VERSION, features[1].getDependencies().get(0).toString()); assertNotNull(features[1].getBundles()); assertEquals(1, features[1].getBundles().size()); assertEquals("b3", features[1].getBundles().get(0).getLocation()); http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/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 42c66d7..e6b5b8f 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 @@ -54,10 +54,10 @@ public class SubsystemTest { addToMapSet(expected, "root/apps1", "b/1.0.0"); SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data1")); - resolver.resolve(Arrays.asList(repo.getFeatures()), + resolver.prepare(Arrays.asList(repo.getFeatures()), features, - Collections.<String, Set<BundleRevision>>emptyMap(), - Collections.<String>emptySet(), + Collections.<String, Set<BundleRevision>>emptyMap()); + resolver.resolve(Collections.<String>emptySet(), FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, null); @@ -85,10 +85,10 @@ public class SubsystemTest { addToMapSet(expected, "root/apps2#f1", "a/1.0.0"); SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data2")); - resolver.resolve(Arrays.asList(repo.getFeatures()), + resolver.prepare(Arrays.asList(repo.getFeatures()), features, - Collections.<String, Set<BundleRevision>>emptyMap(), - Collections.<String>emptySet(), + Collections.<String, Set<BundleRevision>>emptyMap()); + resolver.resolve(Collections.<String>emptySet(), FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, null); @@ -106,10 +106,10 @@ public class SubsystemTest { addToMapSet(expected, "root/apps1", "a/1.0.1"); SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data3")); - resolver.resolve(Arrays.asList(repo.getFeatures()), + resolver.prepare(Arrays.asList(repo.getFeatures()), features, - Collections.<String, Set<BundleRevision>>emptyMap(), - Collections.singleton("b"), + Collections.<String, Set<BundleRevision>>emptyMap()); + resolver.resolve(Collections.singleton("b"), FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, null); @@ -126,12 +126,12 @@ public class SubsystemTest { addToMapSet(expected, "root/apps1", "a/1.0.0"); SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data4")); - resolver.resolve(Arrays.asList(repo.getFeatures()), - features, - Collections.<String, Set<BundleRevision>>emptyMap(), - Collections.<String>emptySet(), - FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, - null); + 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); } @@ -148,12 +148,12 @@ public class SubsystemTest { addToMapSet(expected, "root/apps1", "b/1.0.0"); SubsystemResolver resolver = new SubsystemResolver(new TestDownloadManager(getClass(), "data4")); - resolver.resolve(Arrays.asList(repo.getFeatures()), - features, - Collections.<String, Set<BundleRevision>>emptyMap(), - Collections.<String>emptySet(), - FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE, - null); + 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); } http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/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 42c7c49..723df3b 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 @@ -44,6 +44,7 @@ import static org.apache.karaf.features.FeaturesService.*; import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet; import static org.easymock.EasyMock.anyInt; import static org.easymock.EasyMock.anyObject; +import static org.junit.Assert.fail; public class DeployerTest { @@ -259,6 +260,123 @@ public class DeployerTest { EasyMock.verify(callback); } + @Test + public void testPrerequisite() throws Exception { + + String dataDir = "data2"; + + TestDownloadManager manager = new TestDownloadManager(getClass(), dataDir); + + RepositoryImpl repo = new RepositoryImpl(getClass().getResource(dataDir + "/features.xml").toURI()); + repo.load(true); + Feature f1 = repo.getFeatures()[0]; + Feature f2 = repo.getFeatures()[1]; + + Bundle serviceBundle1 = createTestBundle(1, Bundle.ACTIVE, dataDir, "a100"); + Bundle serviceBundle2 = createTestBundle(2, Bundle.ACTIVE, dataDir, "b100"); + + Deployer.DeployCallback callback = EasyMock.createMock(Deployer.DeployCallback.class); + Deployer deployer = new Deployer(manager, callback); + + callback.print(EasyMock.anyString(), EasyMock.anyBoolean()); + EasyMock.expectLastCall().anyTimes(); + callback.installBundle(EasyMock.eq(ROOT_REGION), EasyMock.eq("a100"), EasyMock.<InputStream>anyObject()); + EasyMock.expectLastCall().andReturn(serviceBundle1); + callback.replaceDigraph(EasyMock.<Map<String, Map<String, Map<String, Set<String>>>>>anyObject(), + EasyMock.<Map<String, Set<Long>>>anyObject()); + EasyMock.expectLastCall(); + callback.saveState(EasyMock.<State>anyObject()); + EasyMock.expectLastCall(); + callback.installFeatureConfigs(f1); + EasyMock.expectLastCall(); + callback.resolveBundles(EasyMock.<Set<Bundle>>anyObject()); + EasyMock.expectLastCall(); + callback.callListeners(EasyMock.<FeatureEvent>anyObject()); + EasyMock.expectLastCall(); + + EasyMock.replay(callback); + + Deployer.DeploymentState dstate = new Deployer.DeploymentState(); + dstate.state = new State(); + dstate.bundles = new HashMap<>(); + dstate.bundlesPerRegion = new HashMap<>(); + dstate.features = new HashMap<>(); + dstate.features.put(f1.getId(), f1); + dstate.features.put(f2.getId(), f2); + dstate.filtersPerRegion = new HashMap<>(); + dstate.filtersPerRegion.put(ROOT_REGION, new HashMap<String, Map<String, Set<String>>>()); + + Deployer.DeploymentRequest request = new Deployer.DeploymentRequest(); + request.bundleUpdateRange = DEFAULT_BUNDLE_UPDATE_RANGE; + request.featureResolutionRange = DEFAULT_FEATURE_RESOLUTION_RANGE; + request.globalRepository = null; + request.options = EnumSet.noneOf(Option.class); + request.overrides = Collections.emptySet(); + request.stateChanges = Collections.emptyMap(); + request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; + request.requestedFeatures = new HashMap<>(); + addToMapSet(request.requestedFeatures, ROOT_REGION, f2.getName()); + + try { + deployer.deploy(dstate, request); + fail("Should have thrown an exception"); + } catch (Deployer.PartialDeploymentException e) { + // ok + } + + EasyMock.verify(callback); + + EasyMock.reset(callback); + + callback.print(EasyMock.anyString(), EasyMock.anyBoolean()); + EasyMock.expectLastCall().anyTimes(); + callback.installBundle(EasyMock.eq(ROOT_REGION), EasyMock.eq("b100"), EasyMock.<InputStream>anyObject()); + EasyMock.expectLastCall().andReturn(serviceBundle2); + callback.replaceDigraph(EasyMock.<Map<String, Map<String, Map<String, Set<String>>>>>anyObject(), + EasyMock.<Map<String, Set<Long>>>anyObject()); + EasyMock.expectLastCall(); + callback.saveState(EasyMock.<State>anyObject()); + EasyMock.expectLastCall(); + callback.installFeatureConfigs(f2); + EasyMock.expectLastCall(); + callback.resolveBundles(EasyMock.<Set<Bundle>>anyObject()); + EasyMock.expectLastCall(); + callback.callListeners(EasyMock.<FeatureEvent>anyObject()); + EasyMock.expectLastCall(); + + EasyMock.replay(callback); + + dstate = new Deployer.DeploymentState(); + dstate.state = new State(); + addToMapSet(dstate.state.installedFeatures, ROOT_REGION, f1.getId()); + dstate.state.stateFeatures.put(ROOT_REGION, Collections.singletonMap(f1.getId(), "Started")); + addToMapSet(dstate.state.managedBundles, ROOT_REGION, serviceBundle1.getBundleId()); + dstate.bundles = new HashMap<>(); + dstate.bundles.put(serviceBundle1.getBundleId(), serviceBundle1); + dstate.bundlesPerRegion = new HashMap<>(); + addToMapSet(dstate.bundlesPerRegion, ROOT_REGION, serviceBundle1.getBundleId()); + dstate.features = new HashMap<>(); + dstate.features.put(f1.getId(), f1); + dstate.features.put(f2.getId(), f2); + dstate.filtersPerRegion = new HashMap<>(); + dstate.filtersPerRegion.put(ROOT_REGION, new HashMap<String, Map<String, Set<String>>>()); + + request = new Deployer.DeploymentRequest(); + request.bundleUpdateRange = DEFAULT_BUNDLE_UPDATE_RANGE; + request.featureResolutionRange = DEFAULT_FEATURE_RESOLUTION_RANGE; + request.globalRepository = null; + request.options = EnumSet.noneOf(Option.class); + request.overrides = Collections.emptySet(); + request.stateChanges = Collections.emptyMap(); + request.updateSnaphots = UPDATE_SNAPSHOTS_NONE; + request.requestedFeatures = new HashMap<>(); + addToMapSet(request.requestedFeatures, ROOT_REGION, f2.getName()); + + deployer.deploy(dstate, request); + + EasyMock.verify(callback); + } + private TestBundle createTestBundle(long bundleId, int state, String dir, String name) throws IOException, BundleException { URL loc = getClass().getResource(dir + "/" + name + ".mf"); Manifest man = new Manifest(loc.openStream()); http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/a100.mf ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/a100.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/a100.mf new file mode 100644 index 0000000..20a7811 --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/a100.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1 +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: a +Bundle-Version: 1.0.0 + http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/b100.mf ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/b100.mf b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/b100.mf new file mode 100644 index 0000000..dc96158 --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/b100.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1 +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: b +Bundle-Version: 1.0.0 + http://git-wip-us.apache.org/repos/asf/karaf/blob/cde64ae2/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/features.xml ---------------------------------------------------------------------- diff --git a/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/features.xml b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/features.xml new file mode 100644 index 0000000..a70c692 --- /dev/null +++ b/features/core/src/test/resources/org/apache/karaf/features/internal/service/data2/features.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. + +--> +<features name="test" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0"> + + <feature name="f1" version="1.0.0"> + <bundle>a100</bundle> + </feature> + + <feature name="f2" version="1.0.0"> + <feature prerequisite="true">f1</feature> + <bundle>b100</bundle> + </feature> + +</features> \ No newline at end of file