You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/04/18 23:35:55 UTC
[5/6] git commit: [KARAF-2923] Region support in features service
[KARAF-2923] Region support in features service
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/2705ad88
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/2705ad88
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/2705ad88
Branch: refs/heads/master
Commit: 2705ad8894a2af462487774c705066ef7de13f25
Parents: 486ad3a
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Fri Apr 18 21:16:02 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Fri Apr 18 23:33:51 2014 +0200
----------------------------------------------------------------------
assemblies/features/standard/pom.xml | 17 -
.../standard/src/main/feature/feature.xml | 6 -
features/command/pom.xml | 5 +
.../features/command/InstallFeatureCommand.java | 5 +-
.../features/command/RegionInfoCommand.java | 129 ++++
.../command/UninstallFeatureCommand.java | 5 +-
features/core/pom.xml | 18 +-
.../java/org/apache/karaf/features/Feature.java | 4 +-
.../apache/karaf/features/FeaturesService.java | 4 +
.../karaf/features/RegionsPersistence.java | 26 -
.../org/apache/karaf/features/Repository.java | 2 -
.../org/apache/karaf/features/Resolver.java | 25 -
.../org/apache/karaf/features/ScopeFilter.java | 25 +
.../java/org/apache/karaf/features/Scoping.java | 29 +
.../internal/deployment/DeploymentBuilder.java | 354 ----------
.../internal/deployment/Downloader.java | 35 -
.../internal/deployment/StreamProvider.java | 26 -
.../internal/download/DownloadCallback.java | 23 +
.../internal/download/DownloadManager.java | 27 +
.../features/internal/download/Downloader.java | 29 +
.../internal/download/StreamProvider.java | 29 +
.../download/simple/SimpleDownloader.java | 120 ++++
.../karaf/features/internal/model/Feature.java | 23 +-
.../karaf/features/internal/model/JaxbUtil.java | 33 +-
.../features/internal/model/ScopeFilter.java | 56 ++
.../karaf/features/internal/model/Scoping.java | 70 ++
.../karaf/features/internal/osgi/Activator.java | 73 +-
.../region/AbstractRegionDigraphVisitor.java | 121 ++++
.../internal/region/CandidateComparator.java | 125 ++++
.../internal/region/ResourceComparator.java | 43 ++
.../features/internal/region/Subsystem.java | 341 ++++++++++
.../region/SubsystemResolveContext.java | 188 ++++++
.../internal/region/SubsystemResolver.java | 317 +++++++++
.../internal/resolver/CandidateComparator.java | 129 ----
.../internal/resolver/FeatureNamespace.java | 72 --
.../internal/resolver/FeatureResource.java | 43 +-
.../internal/resolver/IdentityCapability.java | 2 +-
.../internal/resolver/ResolveContextImpl.java | 102 ---
.../internal/resolver/ResourceBuilder.java | 7 +-
.../internal/resolver/ResourceUtils.java | 103 +++
.../internal/resolver/UriNamespace.java | 47 --
.../service/FeatureConfigInstaller.java | 40 +-
.../internal/service/FeaturesServiceImpl.java | 667 ++++++++++++-------
.../internal/service/RepositoryImpl.java | 8 +-
.../internal/service/SimpleDownloader.java | 51 --
.../karaf/features/internal/service/State.java | 29 +-
.../features/internal/service/StateStorage.java | 131 ++--
.../features/internal/util/JsonWriter.java | 8 +
.../karaf/features/internal/util/MapUtils.java | 65 ++
.../equinox/internal/region/DigraphHelper.java | 149 +++++
.../karaf/features/karaf-features-1.3.0.xsd | 58 +-
.../karaf/features/FeaturesServiceTest.java | 11 +-
.../features/internal/region/SubsystemTest.java | 213 ++++++
.../service/FeaturesServiceImplTest.java | 8 +-
.../internal/service/OverridesTest.java | 13 +-
.../internal/service/StateStorageTest.java | 80 +++
.../karaf/features/internal/region/data1/a.mf | 6 +
.../karaf/features/internal/region/data1/b.mf | 6 +
.../karaf/features/internal/region/data1/c.mf | 6 +
.../features/internal/region/data1/features.xml | 31 +
.../karaf/features/internal/region/data2/a.mf | 7 +
.../karaf/features/internal/region/data2/b.mf | 7 +
.../karaf/features/internal/region/data2/c.mf | 6 +
.../karaf/features/internal/region/data2/d.mf | 6 +
.../karaf/features/internal/region/data2/e.mf | 6 +
.../features/internal/region/data2/features.xml | 42 ++
.../karaf/features/internal/service/f07.xml | 11 +
.../org/apache/karaf/itests/RegionTest.java | 48 --
.../itests/features/StandardFeaturesTest.java | 5 -
pom.xml | 14 +-
region/NOTICE | 71 --
region/pom.xml | 136 ----
.../karaf/region/commands/AddBundleCommand.java | 52 --
.../karaf/region/commands/AddFilterCommand.java | 169 -----
.../karaf/region/commands/AddRegionCommand.java | 38 --
.../karaf/region/commands/InfoCommand.java | 117 ----
.../region/commands/RegionCommandSupport.java | 79 ---
.../karaf/region/commands/util/FileUtil.java | 177 -----
.../region/persist/internal/Activator.java | 91 ---
.../persist/internal/RegionsBundleTracker.java | 77 ---
.../internal/RegionsPersistenceImpl.java | 203 ------
.../internal/model/FilterAttributeType.java | 94 ---
.../internal/model/FilterBundleType.java | 156 -----
.../internal/model/FilterNamespaceType.java | 102 ---
.../internal/model/FilterPackageType.java | 129 ----
.../persist/internal/model/FilterType.java | 195 ------
.../persist/internal/model/ObjectFactory.java | 116 ----
.../internal/model/RegionBundleType.java | 94 ---
.../persist/internal/model/RegionType.java | 106 ---
.../persist/internal/model/RegionsType.java | 112 ----
.../persist/internal/model/package-info.java | 9 -
.../internal/util/ManifestHeaderProcessor.java | 661 ------------------
.../internal/util/ManifestHeaderUtils.java | 85 ---
.../persist/internal/util/VersionRange.java | 456 -------------
.../org/apache/karaf/region/persist/region.xsd | 109 ---
.../webconsole/features/ExtendedFeature.java | 6 +-
96 files changed, 3127 insertions(+), 5083 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/assemblies/features/standard/pom.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/standard/pom.xml b/assemblies/features/standard/pom.xml
index f050441..bad1015 100644
--- a/assemblies/features/standard/pom.xml
+++ b/assemblies/features/standard/pom.xml
@@ -162,11 +162,6 @@
<classifier>uber</classifier>
<scope>provided</scope>
</dependency>
- <dependency>
- <groupId>org.apache.karaf.features</groupId>
- <artifactId>org.apache.karaf.features.obr</artifactId>
- <scope>provided</scope>
- </dependency>
<!-- config deps -->
<dependency>
@@ -175,18 +170,6 @@
<scope>provided</scope>
</dependency>
- <!-- region deps -->
- <dependency>
- <groupId>org.eclipse.equinox</groupId>
- <artifactId>org.eclipse.equinox.region</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.karaf.region</groupId>
- <artifactId>org.apache.karaf.region.core</artifactId>
- <scope>provided</scope>
- </dependency>
-
<!-- jetty deps -->
<dependency>
<groupId>org.apache.servicemix.specs</groupId>
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/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 f50cd65..e7d77c5 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -147,12 +147,6 @@
<bundle start-level="30" start="true">mvn:org.apache.karaf.log/org.apache.karaf.log.core/${project.version}</bundle>
</feature>
- <feature name="region" description="Provide Region Support" version="${project.version}">
- <bundle dependency="true" start-level="20">mvn:org.apache.aries/org.apache.aries.util/${aries.util.version}</bundle>
- <bundle start-level="30">mvn:org.eclipse.equinox/org.eclipse.equinox.region/${equinox.region.version}</bundle>
- <bundle start-level="30">mvn:org.apache.karaf.region/org.apache.karaf.region.core/${project.version}</bundle>
- </feature>
-
<feature name="package" version="${project.version}" description="Package commands and mbeans">
<bundle start-level="30">mvn:org.apache.karaf.package/org.apache.karaf.package.core/${project.version}</bundle>
</feature>
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/command/pom.xml
----------------------------------------------------------------------
diff --git a/features/command/pom.xml b/features/command/pom.xml
index 4f49bb1..5978b38 100644
--- a/features/command/pom.xml
+++ b/features/command/pom.xml
@@ -56,6 +56,11 @@
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.core</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.equinox</groupId>
+ <artifactId>org.eclipse.equinox.region</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
index b7f8184..ccd869a 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/InstallFeatureCommand.java
@@ -50,6 +50,9 @@ public class InstallFeatureCommand extends FeaturesCommandSupport {
@Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only", required = false, multiValued = false)
boolean simulate;
+ @Option(name = "-g", aliases = "--region", description = "Region to install to")
+ String region;
+
protected void doExecute(FeaturesService admin) throws Exception {
EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class);
if (simulate) {
@@ -64,6 +67,6 @@ public class InstallFeatureCommand extends FeaturesCommandSupport {
if (verbose) {
options.add(FeaturesService.Option.Verbose);
}
- admin.installFeatures(new HashSet<String>(features), options);
+ admin.installFeatures(new HashSet<String>(features), region, options);
}
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/command/src/main/java/org/apache/karaf/features/command/RegionInfoCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/RegionInfoCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/RegionInfoCommand.java
new file mode 100644
index 0000000..8a3e73e
--- /dev/null
+++ b/features/command/src/main/java/org/apache/karaf/features/command/RegionInfoCommand.java
@@ -0,0 +1,129 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+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;
+import org.eclipse.equinox.region.Region;
+import org.eclipse.equinox.region.RegionDigraph;
+import org.eclipse.equinox.region.RegionFilter;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+@Command(scope = "feature", name = "regions", description = "Prints information about region digraph.")
+@Service
+public class RegionInfoCommand implements Action {
+
+ @Option(name = "-v", aliases = "--verbose", required = false, description = "Show all info.")
+ boolean verbose;
+
+ @Option(name = "-b", aliases = "--bundles", required = false, description = "Show bundles in each region.")
+ boolean bundles;
+
+ @Option(name = "-f", aliases = "--filters", required = false, description = "Show filters.")
+ boolean filters;
+
+ @Option(name = "-n", aliases = "--namespaces", required = false, description = "Show namespaces in each filter.")
+ boolean namespaces;
+
+ @Argument(index = 0, name = "regions", description = "Regions to provide detailed info for.", required = false, multiValued = true)
+ List<String> regions;
+
+ @Reference
+ RegionDigraph regionDigraph;
+
+ @Reference
+ BundleContext bundleContext;
+
+ public Object execute() throws Exception {
+ System.out.println("Regions");
+ if (regions == null) {
+ for (Region region : regionDigraph.getRegions()) {
+ showRegion(region);
+ }
+ } else {
+ bundles = true;
+ filters = true;
+ namespaces = true;
+ for (String regionName : regions) {
+ Region region = regionDigraph.getRegion(regionName);
+ if (region == null) {
+ System.out.println("No region " + regionName);
+ } else {
+ showRegion(region);
+ }
+ }
+ }
+ return null;
+ }
+
+ private void showRegion(Region region) {
+ BundleContext bundleContext = this.bundleContext.getBundle(0).getBundleContext();
+ System.out.println(region.getName());
+ if (verbose || bundles) {
+ for (Long id : new TreeSet<Long>(region.getBundleIds())) {
+ Bundle b = bundleContext.getBundle(id);
+ System.out.println(String.format(" %3d %s%s", id, getStateString(b), b));
+ }
+ }
+ if (verbose || filters || namespaces) {
+ for (RegionDigraph.FilteredRegion f : region.getEdges()) {
+ System.out.println(" filter to " + f.getRegion().getName());
+ if (verbose || namespaces) {
+ RegionFilter rf = f.getFilter();
+ for (Map.Entry<String, Collection<String>> policy : rf.getSharingPolicy().entrySet()) {
+ String namespace = policy.getKey();
+ System.out.println(" namespace: " + namespace);
+ for (String e : policy.getValue()) {
+ System.out.println(" " + e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public String getStateString(Bundle bundle) {
+ if (bundle == null) {
+ return "Bundle null ";
+ }
+ int state = bundle.getState();
+ if (state == Bundle.ACTIVE) {
+ return "Active ";
+ } else if (state == Bundle.INSTALLED) {
+ return "Installed ";
+ } else if (state == Bundle.RESOLVED) {
+ return "Resolved ";
+ } else if (state == Bundle.STARTING) {
+ return "Starting ";
+ } else if (state == Bundle.STOPPING) {
+ return "Stopping ";
+ } else {
+ return "Unknown ";
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
index e62f697..0fb7d14 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
@@ -45,6 +45,9 @@ public class UninstallFeatureCommand extends FeaturesCommandSupport {
@Option(name = "-t", aliases = "--simulate", description = "Perform a simulation only", required = false, multiValued = false)
boolean simulate;
+ @Option(name = "-g", aliases = "--region", description = "Region to install to")
+ String region;
+
protected void doExecute(FeaturesService admin) throws Exception {
// iterate in the provided feature
EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class);
@@ -57,6 +60,6 @@ public class UninstallFeatureCommand extends FeaturesCommandSupport {
if (verbose) {
options.add(FeaturesService.Option.Verbose);
}
- admin.uninstallFeatures(new HashSet<String>(features), options);
+ admin.uninstallFeatures(new HashSet<String>(features), region, options);
}
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/pom.xml
----------------------------------------------------------------------
diff --git a/features/core/pom.xml b/features/core/pom.xml
index d8a2620..8998ba9 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -72,6 +72,12 @@
</dependency>
<dependency>
+ <groupId>org.eclipse.equinox</groupId>
+ <artifactId>org.eclipse.equinox.region</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
@@ -110,8 +116,15 @@
org.apache.karaf.features;
org.apache.karaf.features.management;
org.apache.karaf.features.management.codec;
- -noimport:=true
+ -noimport:=true,
+ org.osgi.service.resolver;-split-package:=merge-first,
+ org.osgi.service.repository,
+ org.eclipse.equinox.region.*
</Export-Package>
+ <Import-Package>
+ !org.eclipse.osgi.service.resolver,
+ *
+ </Import-Package>
<Provide-Capability>
service-reference;effective:=active;objectClass=org.apache.karaf.features.FeaturesService
</Provide-Capability>
@@ -123,8 +136,7 @@
org.apache.karaf.util.collections,
org.apache.karaf.util.json,
org.apache.karaf.util.tracker,
- org.osgi.service.resolver,
- org.osgi.service.repository
+ org.eclipse.equinox.internal.region.*;-split-package:=merge-first,
</Private-Package>
<Embed-Dependency>
org.apache.karaf.util;inline="org/apache/karaf/util/XmlUtils*.class"
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/Feature.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Feature.java b/features/core/src/main/java/org/apache/karaf/features/Feature.java
index 2f9f001..06235e2 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Feature.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Feature.java
@@ -54,10 +54,10 @@ public interface Feature {
int getStartLevel();
- String getRegion();
-
List<? extends Capability> getCapabilities();
List<? extends Requirement> getRequirements();
+ Scoping getScoping();
+
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/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 ef3dbcf..7bb3cf8 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
@@ -71,6 +71,8 @@ public interface FeaturesService {
void installFeatures(Set<String> features, EnumSet<Option> options) throws Exception;
+ void installFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception;
+
void uninstallFeature(String name, EnumSet<Option> options) throws Exception;
void uninstallFeature(String name) throws Exception;
@@ -81,6 +83,8 @@ public interface FeaturesService {
void uninstallFeatures(Set<String> features, EnumSet<Option> options) throws Exception;
+ void uninstallFeatures(Set<String> features, String region, EnumSet<Option> options) throws Exception;
+
Feature[] listFeatures() throws Exception;
Feature[] listRequiredFeatures() throws Exception;
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/RegionsPersistence.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/RegionsPersistence.java b/features/core/src/main/java/org/apache/karaf/features/RegionsPersistence.java
deleted file mode 100644
index 96ca7da..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/RegionsPersistence.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-
-public interface RegionsPersistence {
- void install(Bundle b, String regionName) throws BundleException;
-}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/Repository.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Repository.java b/features/core/src/main/java/org/apache/karaf/features/Repository.java
index 3ea12ec..84d4a50 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Repository.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Repository.java
@@ -32,6 +32,4 @@ public interface Repository {
Feature[] getFeatures() throws Exception;
- boolean isValid();
-
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/Resolver.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Resolver.java b/features/core/src/main/java/org/apache/karaf/features/Resolver.java
deleted file mode 100644
index d2fa941..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/Resolver.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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;
-
-import java.util.List;
-
-public interface Resolver {
-
- List<BundleInfo> resolve(Feature feature) throws Exception;
-
-}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/ScopeFilter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/ScopeFilter.java b/features/core/src/main/java/org/apache/karaf/features/ScopeFilter.java
new file mode 100644
index 0000000..c4ddbfe
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/ScopeFilter.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+public interface ScopeFilter {
+
+ String getNamespace();
+
+ String getFilter();
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/Scoping.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Scoping.java b/features/core/src/main/java/org/apache/karaf/features/Scoping.java
new file mode 100644
index 0000000..b97e6f2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/Scoping.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+import java.util.List;
+
+public interface Scoping {
+
+ boolean acceptDependencies();
+
+ List<? extends ScopeFilter> getImports();
+
+ List<? extends ScopeFilter> getExports();
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java
deleted file mode 100644
index c3ac2b7..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/DeploymentBuilder.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * 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.internal.deployment;
-
-import java.io.IOException;
-import java.io.InputStream;
-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;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import org.apache.felix.resolver.ResolverImpl;
-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.Conditional;
-import org.apache.karaf.features.Dependency;
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.Repository;
-import org.apache.karaf.features.internal.repository.AggregateRepository;
-import org.apache.karaf.features.internal.repository.StaticRepository;
-import org.apache.karaf.features.internal.resolver.FeatureNamespace;
-import org.apache.karaf.features.internal.resolver.FeatureResource;
-import org.apache.karaf.features.internal.resolver.RequirementImpl;
-import org.apache.karaf.features.internal.resolver.ResolveContextImpl;
-import org.apache.karaf.features.internal.resolver.ResourceBuilder;
-import org.apache.karaf.features.internal.resolver.ResourceImpl;
-import org.apache.karaf.features.internal.resolver.Slf4jResolverLog;
-import org.apache.karaf.features.internal.service.Overrides;
-import org.apache.karaf.features.internal.util.Macro;
-import org.apache.karaf.features.internal.util.MultiException;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Version;
-import org.osgi.framework.namespace.IdentityNamespace;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
-import org.osgi.resource.Wire;
-import org.osgi.service.resolver.ResolutionException;
-import org.osgi.service.resolver.ResolveContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- */
-public class DeploymentBuilder {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(DeploymentBuilder.class);
-
- public static final String REQ_PROTOCOL = "req:";
-
- private final Collection<Repository> repositories;
-
- private final List<org.osgi.service.repository.Repository> resourceRepos;
-
- String featureRange = "${range;[====,====]}";
-
- Downloader downloader;
- ResourceImpl requirements;
- Map<String, Resource> resources;
- Set<Resource> optionals;
- Map<String, StreamProvider> providers;
-
- Set<Feature> featuresToRegister = new HashSet<Feature>();
-
- public DeploymentBuilder(Downloader downloader,
- Collection<Repository> repositories) {
- this.downloader = downloader;
- this.repositories = repositories;
- this.resourceRepos = new ArrayList<org.osgi.service.repository.Repository>();
- }
-
- public void addResourceRepository(org.osgi.service.repository.Repository repository) {
- resourceRepos.add(repository);
- }
-
- public Map<String, StreamProvider> getProviders() {
- return providers;
- }
-
- public void setFeatureRange(String featureRange) {
- this.featureRange = featureRange;
- }
-
- public Map<String, Resource> download(
- Set<String> features,
- Set<String> bundles,
- Set<String> reqs,
- Set<String> overrides,
- Set<String> optionals)
- throws IOException, MultiException, InterruptedException, ResolutionException, BundleException {
- this.resources = new ConcurrentHashMap<String, Resource>();
- this.optionals = new HashSet<Resource>();
- this.providers = new ConcurrentHashMap<String, StreamProvider>();
- this.requirements = new ResourceImpl("dummy", "dummy", Version.emptyVersion);
- // First, gather all bundle resources
- for (String feature : features) {
- registerMatchingFeatures(feature);
- }
- for (String bundle : bundles) {
- downloadAndBuildResource(bundle);
- }
- for (String req : reqs) {
- buildRequirement(req);
- }
- for (String override : overrides) {
- // TODO: ignore download failures for overrides
- downloadAndBuildResource(Overrides.extractUrl(override));
- }
- for (String optional : optionals) {
- downloadAndBuildResource(optional);
- }
- // Wait for all resources to be created
- downloader.await();
- // Do override replacement
- Overrides.override(resources, overrides);
- // Build features resources
- for (Feature feature : featuresToRegister) {
- Resource resource = FeatureResource.build(feature, featureRange, resources);
- resources.put("feature:" + feature.getName() + "/" + feature.getVersion(), resource);
- for (Conditional cond : feature.getConditional()) {
- this.optionals.add(FeatureResource.build(feature, cond, featureRange, resources));
- }
- }
- // Build requirements
- for (String feature : features) {
- requireFeature(feature);
- }
- for (String bundle : bundles) {
- requireResource(bundle);
- }
- for (String req : reqs) {
- requireResource(REQ_PROTOCOL + req);
- }
- return resources;
- }
-
- public Map<Resource, List<Wire>> resolve(List<Resource> systemBundles) throws ResolutionException {
- // Resolve
- for (int i = 0; i < systemBundles.size(); i++) {
- resources.put("system-bundle-" + i, systemBundles.get(i));
- }
-
- List<org.osgi.service.repository.Repository> repos = new ArrayList<org.osgi.service.repository.Repository>();
- repos.add(new StaticRepository(resources.values()));
- repos.addAll(resourceRepos);
-
- ResolverImpl resolver = new ResolverImpl(new Slf4jResolverLog(LOGGER));
- ResolveContext context = new ResolveContextImpl(
- Collections.<Resource>singleton(requirements),
- Collections.<Resource>emptySet(),
- new AggregateRepository(repos),
- false);
- Map<Resource, List<Wire>> best = resolver.resolve(context);
-
- // TODO: we actually need to use multiple passes for conditionals
- // TODO: but it may be optimized by passing the old wiring instead
- // TODO: of computing everything again
- Set<Resource> resources = new HashSet<Resource>();
- resources.add(requirements);
- for (Resource optional : optionals) {
- try {
- Set<Resource> newSet = new HashSet<Resource>(resources);
- newSet.add(optional);
- context = new ResolveContextImpl(
- newSet,
- Collections.<Resource>emptySet(),
- new AggregateRepository(repos),
- false);
- best = resolver.resolve(context);
- resources = newSet;
- } catch (ResolutionException e) {
- // Ignore this resource
- }
- }
- return best;
- }
-
- public void requireFeature(String feature) throws IOException {
- // Find name and version range
- String[] split = feature.split("/");
- String name = split[0].trim();
- String version = (split.length > 1) ? split[1].trim() : null;
- if (version != null && !version.equals("0.0.0") && !version.startsWith("[") && !version.startsWith("(")) {
- version = Macro.transform(featureRange, version);
- }
- VersionRange range = version != null ? new VersionRange(version) : VersionRange.ANY_VERSION;
- // Add requirement
- Map<String, Object> attrs = new HashMap<String, Object>();
- attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, name);
- attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, FeatureNamespace.TYPE_FEATURE);
- attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, range);
- requirements.addRequirement(
- new RequirementImpl(requirements, IdentityNamespace.IDENTITY_NAMESPACE,
- Collections.<String, String>emptyMap(), attrs)
- );
- }
-
- public void requireResource(String location) {
- Resource res = resources.get(location);
- if (res == null) {
- throw new IllegalStateException("Could not find resource for " + location);
- }
- List<Capability> caps = res.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
- if (caps.size() != 1) {
- throw new IllegalStateException("Resource does not have a single " + IdentityNamespace.IDENTITY_NAMESPACE + " capability");
- }
- Capability cap = caps.get(0);
- // Add requirement
- Map<String, Object> attrs = new HashMap<String, Object>();
- attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE));
- attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, cap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE));
- attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((Version) cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE), true));
- requirements.addRequirement(
- new RequirementImpl(requirements, IdentityNamespace.IDENTITY_NAMESPACE,
- Collections.<String, String>emptyMap(), attrs));
-
- }
-
- public void registerMatchingFeatures(String feature) throws IOException {
- // Find name and version range
- String[] split = feature.split("/");
- String name = split[0].trim();
- String version = (split.length > 1)
- ? split[1].trim() : Version.emptyVersion.toString();
- // Register matching features
- registerMatchingFeatures(name, new VersionRange(version));
- }
-
- public void registerMatchingFeatures(String name, String version) throws IOException {
- if (version != null && !version.equals("0.0.0") && !version.startsWith("[") && !version.startsWith("(")) {
- version = Macro.transform(featureRange, version);
- }
- registerMatchingFeatures(name, version != null ? new VersionRange(version) : VersionRange.ANY_VERSION);
- }
-
- public void registerMatchingFeatures(String name, VersionRange range) throws IOException {
- for (Repository repo : repositories) {
- Feature[] features;
- try {
- features = repo.getFeatures();
- } catch (Exception e) {
- // This should not happen as the repository has been loaded already
- throw new IllegalStateException(e);
- }
- for (Feature f : features) {
- if (name.equals(f.getName())) {
- Version v = VersionTable.getVersion(f.getVersion());
- if (range.contains(v)) {
- featuresToRegister.add(f);
- for (Dependency dep : f.getDependencies()) {
- registerMatchingFeatures(dep.getName(), dep.getVersion());
- }
- for (BundleInfo bundle : f.getBundles()) {
- downloadAndBuildResource(bundle.getLocation());
- }
- for (Conditional cond : f.getConditional()) {
- Feature c = cond.asFeature(f.getName(), f.getVersion());
- featuresToRegister.add(c);
- for (BundleInfo bundle : c.getBundles()) {
- downloadAndBuildResource(bundle.getLocation());
- }
- }
- }
- }
- }
- }
- }
-
- public void buildRequirement(String requirement) {
- try {
- String location = REQ_PROTOCOL + requirement;
- ResourceImpl resource = new ResourceImpl(location, "dummy", Version.emptyVersion);
- for (Requirement req : ResourceBuilder.parseRequirement(resource, requirement)) {
- resource.addRequirement(req);
- }
- resources.put(location, resource);
- } catch (BundleException e) {
- throw new IllegalArgumentException("Error parsing requirement: " + requirement, e);
- }
- }
-
- public void downloadAndBuildResource(final String location) throws IOException {
- if (!resources.containsKey(location)) {
- downloader.download(location, new Downloader.DownloadCallback() {
- @Override
- public void downloaded(StreamProvider provider) throws Exception {
- manageResource(location, provider);
- }
- });
- }
- }
-
- private void manageResource(String location, StreamProvider provider) throws Exception {
- if (!resources.containsKey(location)) {
- Attributes attributes = getAttributes(location, provider);
- Resource resource = createResource(location, attributes);
- resources.put(location, resource);
- providers.put(location, provider);
- }
- }
-
- private Resource createResource(String uri, Attributes attributes) throws Exception {
- Map<String, String> headers = new HashMap<String, String>();
- for (Map.Entry attr : attributes.entrySet()) {
- headers.put(attr.getKey().toString(), attr.getValue().toString());
- }
- try {
- return ResourceBuilder.build(uri, headers);
- } catch (BundleException e) {
- throw new Exception("Unable to create resource for bundle " + uri, e);
- }
- }
-
- protected Attributes getAttributes(String uri, StreamProvider provider) throws Exception {
- InputStream is = provider.open();
- try {
- ZipInputStream zis = new ZipInputStream(is);
- ZipEntry entry;
- while ( (entry = zis.getNextEntry()) != null ) {
- if ("META-INF/MANIFEST.MF".equals(entry.getName())) {
- return new Manifest(zis).getMainAttributes();
- }
- }
- } finally {
- is.close();
- }
- throw new IllegalArgumentException("Resource " + uri + " does not contain a manifest");
- }
-
-}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java
deleted file mode 100644
index 2d5dd98..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/Downloader.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.internal.deployment;
-
-import java.net.MalformedURLException;
-
-import org.apache.karaf.features.internal.util.MultiException;
-
-public interface Downloader {
-
- void await() throws InterruptedException, MultiException;
-
- void download(String location, DownloadCallback downloadCallback) throws MalformedURLException;
-
- interface DownloadCallback {
-
- void downloaded(StreamProvider provider) throws Exception;
-
- }
-
-}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java b/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java
deleted file mode 100644
index 60a3dfc..0000000
--- a/features/core/src/main/java/org/apache/karaf/features/internal/deployment/StreamProvider.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.internal.deployment;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public interface StreamProvider {
-
- InputStream open() throws IOException;
-
-}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadCallback.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadCallback.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadCallback.java
new file mode 100644
index 0000000..b8fda6a
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadCallback.java
@@ -0,0 +1,23 @@
+/*
+ * 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.internal.download;
+
+public interface DownloadCallback {
+
+ void downloaded(StreamProvider provider) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManager.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManager.java
new file mode 100644
index 0000000..0bac194
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/DownloadManager.java
@@ -0,0 +1,27 @@
+/*
+ * 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.internal.download;
+
+import java.util.Map;
+
+public interface DownloadManager {
+
+ Downloader createDownloader();
+
+ Map<String,StreamProvider> getProviders();
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/download/Downloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/Downloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/Downloader.java
new file mode 100644
index 0000000..baf71a0
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/Downloader.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.download;
+
+import java.net.MalformedURLException;
+
+import org.apache.karaf.features.internal.util.MultiException;
+
+public interface Downloader {
+
+ void await() throws InterruptedException, MultiException;
+
+ void download(String location, DownloadCallback downloadCallback) throws MalformedURLException;
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
new file mode 100644
index 0000000..c25fa43
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/StreamProvider.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.download;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+public interface StreamProvider {
+
+ InputStream open() throws IOException;
+
+ Map<String, String> getMetadata() throws IOException;
+
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
new file mode 100644
index 0000000..4569fe2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/download/simple/SimpleDownloader.java
@@ -0,0 +1,120 @@
+/*
+ * 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.internal.download.simple;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.karaf.features.internal.download.DownloadCallback;
+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.util.MultiException;
+
+public class SimpleDownloader implements DownloadManager, Downloader {
+
+ protected final MultiException exception = new MultiException("Error");
+
+ protected final ConcurrentMap<String, StreamProvider> providers = new ConcurrentHashMap<String, StreamProvider>();
+
+ @Override
+ public Downloader createDownloader() {
+ return this;
+ }
+
+ @Override
+ public void await() throws InterruptedException, MultiException {
+ exception.throwIfExceptions();
+ }
+
+ @Override
+ public void download(final String location, final DownloadCallback downloadCallback) throws MalformedURLException {
+ if (!providers.containsKey(location)) {
+ providers.putIfAbsent(location, createProvider(location));
+ }
+ try {
+ downloadCallback.downloaded(providers.get(location));
+ } catch (Exception e) {
+ exception.addException(e);
+ }
+ }
+
+ protected StreamProvider createProvider(String location) throws MalformedURLException {
+ return new UrlProvider(new URL(location));
+ }
+
+ public Map<String, StreamProvider> getProviders() {
+ return providers;
+ }
+
+ static class UrlProvider implements StreamProvider {
+ private final URL url;
+ private volatile Map<String, String> metadata;
+
+ UrlProvider(URL url) {
+ this.url = url;
+ }
+
+ @Override
+ public InputStream open() throws IOException {
+ return url.openStream();
+ }
+
+ @Override
+ public Map<String, String> getMetadata() throws IOException {
+ if (metadata == null) {
+ synchronized (this) {
+ if (metadata == null) {
+ metadata = doGetMetadata();
+ }
+ }
+ }
+ return metadata;
+ }
+
+ protected Map<String, String> doGetMetadata() throws IOException {
+ InputStream is = open();
+ try {
+ ZipInputStream zis = new ZipInputStream(is);
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ if ("META-INF/MANIFEST.MF".equals(entry.getName())) {
+ Attributes attributes = new Manifest(zis).getMainAttributes();
+ Map<String, String> headers = new HashMap<String, String>();
+ for (Map.Entry attr : attributes.entrySet()) {
+ headers.put(attr.getKey().toString(), attr.getValue().toString());
+ }
+ return headers;
+ }
+ }
+ } finally {
+ is.close();
+ }
+ throw new IllegalArgumentException("Resource " + url + " does not contain a manifest");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/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 46580da..0aa5eb5 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
@@ -75,7 +75,8 @@ import javax.xml.bind.annotation.XmlType;
"bundle",
"conditional",
"capability",
- "requirement"
+ "requirement",
+ "scoping"
})
public class Feature extends Content implements org.apache.karaf.features.Feature {
public static String SPLIT_FOR_NAME_AND_VERSION = "/";
@@ -95,11 +96,10 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
protected String install;
@XmlAttribute(name = "start-level")
protected Integer startLevel;
- @XmlAttribute
- protected String region;
protected List<Conditional> conditional;
protected List<Capability> capability;
protected List<Requirement> requirement;
+ protected Scoping scoping;
public Feature() {
}
@@ -280,15 +280,6 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
this.startLevel = value;
}
-
- public String getRegion() {
- return region;
- }
-
- public void setRegion(String region) {
- this.region = region;
- }
-
/**
* Gets the value of the conditional property.
* <p/>
@@ -330,6 +321,14 @@ public class Feature extends Content implements org.apache.karaf.features.Featur
return this.requirement;
}
+ public Scoping getScoping() {
+ return scoping;
+ }
+
+ public void setScoping(Scoping scoping) {
+ this.scoping = scoping;
+ }
+
public String toString() {
return getId();
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
index 2036452..9b5d7b6 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
@@ -41,6 +41,8 @@ import javax.xml.validation.SchemaFactory;
import org.apache.karaf.features.FeaturesNamespaces;
import org.apache.karaf.util.XmlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -53,6 +55,7 @@ import org.xml.sax.helpers.XMLFilterImpl;
public class JaxbUtil {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JaxbUtil.class);
private static final JAXBContext FEATURES_CONTEXT;
static {
try {
@@ -112,11 +115,16 @@ public class JaxbUtil {
doc = XmlUtils.parse(uri);
}
- Schema schema = getSchema(doc.getDocumentElement().getNamespaceURI());
- try {
- schema.newValidator().validate(new DOMSource(doc));
- } catch (SAXException e) {
- throw new IllegalArgumentException("Unable to validate " + uri, e);
+ String nsuri = doc.getDocumentElement().getNamespaceURI();
+ if (nsuri == null) {
+ LOGGER.warn("Old style feature file without namespace found (URI: {}). This format is deprecated and support for it will soon be removed", uri);
+ } else {
+ Schema schema = getSchema(nsuri);
+ try {
+ schema.newValidator().validate(new DOMSource(doc));
+ } catch (SAXException e) {
+ throw new IllegalArgumentException("Unable to validate " + uri, e);
+ }
}
fixDom(doc, doc.getDocumentElement());
@@ -162,12 +170,14 @@ public class JaxbUtil {
private static void fixDom(Document doc, Node node) {
- if (node.getNamespaceURI() != null && !FeaturesNamespaces.URI_CURRENT.equals(node.getNamespaceURI())) {
- doc.renameNode(node, FeaturesNamespaces.URI_CURRENT, node.getLocalName());
- }
- NodeList children = node.getChildNodes();
- for (int i = 0; i < children.getLength(); i++) {
- fixDom(doc, children.item(i));
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ if (!FeaturesNamespaces.URI_CURRENT.equals(node.getNamespaceURI())) {
+ doc.renameNode(node, FeaturesNamespaces.URI_CURRENT, node.getLocalName());
+ }
+ NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ fixDom(doc, children.item(i));
+ }
}
}
@@ -177,7 +187,6 @@ public class JaxbUtil {
XMLFilter xmlFilter = new NoSourceAndNamespaceFilter(XmlUtils.xmlReader());
xmlFilter.setContentHandler(unmarshaller.getUnmarshallerHandler());
-
InputSource is = new InputSource(uri);
if (stream != null) {
is.setByteStream(stream);
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/model/ScopeFilter.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/ScopeFilter.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/ScopeFilter.java
new file mode 100644
index 0000000..4b81fed
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/ScopeFilter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.internal.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "scopeFilter", propOrder = {
+ "value"
+})
+public class ScopeFilter implements org.apache.karaf.features.ScopeFilter {
+
+ @XmlAttribute(required = true)
+ protected String namespace;
+ @XmlValue
+ protected String value;
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String getFilter() {
+ return getValue();
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/model/Scoping.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Scoping.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Scoping.java
new file mode 100644
index 0000000..6773995
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Scoping.java
@@ -0,0 +1,70 @@
+/*
+ * 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.internal.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "scoping", propOrder = {
+ "imports",
+ "exports"
+})
+public class Scoping implements org.apache.karaf.features.Scoping {
+
+ @XmlAttribute
+ boolean acceptDependencies;
+ @XmlElement(name = "import")
+ List<ScopeFilter> imports;
+ @XmlElement(name = "export")
+ List<ScopeFilter> exports;
+
+ public List<ScopeFilter> getImport() {
+ if (imports == null) {
+ imports = new ArrayList<ScopeFilter>();
+ }
+ return imports;
+ }
+
+ public List<ScopeFilter> getExport() {
+ if (exports == null) {
+ exports = new ArrayList<ScopeFilter>();
+ }
+ return exports;
+ }
+
+ @Override
+ public boolean acceptDependencies() {
+ return acceptDependencies;
+ }
+
+ @Override
+ public List<? extends org.apache.karaf.features.ScopeFilter> getImports() {
+ return getImport();
+ }
+
+ @Override
+ public List<? extends org.apache.karaf.features.ScopeFilter> getExports() {
+ return getExport();
+ }
+}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index be0da05..0551d21 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -27,8 +27,10 @@ import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Properties;
+import org.apache.felix.resolver.ResolverImpl;
import org.apache.karaf.features.FeaturesListener;
import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.features.internal.resolver.Slf4jResolverLog;
import org.apache.karaf.features.internal.service.EventAdminListener;
import org.apache.karaf.features.internal.service.FeatureConfigInstaller;
import org.apache.karaf.features.internal.service.FeatureFinder;
@@ -36,25 +38,35 @@ import org.apache.karaf.features.internal.service.BootFeaturesInstaller;
import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
import org.apache.karaf.features.internal.service.StateStorage;
import org.apache.karaf.features.internal.management.FeaturesServiceMBeanImpl;
-import org.apache.karaf.features.RegionsPersistence;
import org.apache.karaf.util.tracker.BaseActivator;
import org.apache.karaf.util.tracker.SingleServiceTracker;
+import org.eclipse.equinox.internal.region.DigraphHelper;
+import org.eclipse.equinox.internal.region.StandardRegionDigraph;
+import org.eclipse.equinox.internal.region.management.StandardManageableRegionDigraph;
+import org.eclipse.equinox.region.RegionDigraph;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.bundle.CollisionHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ManagedService;
+import org.osgi.service.resolver.Resolver;
import org.osgi.service.url.URLStreamHandlerService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.LoggerFactory;
public class Activator extends BaseActivator {
public static final String FEATURES_REPOS_PID = "org.apache.karaf.features.repos";
public static final String FEATURES_SERVICE_CONFIG_FILE = "org.apache.karaf.features.cfg";
+ private static final String STATE_FILE = "state.json";
+
private ServiceTracker<FeaturesListener, FeaturesListener> featuresListenerTracker;
private FeaturesServiceImpl featuresService;
- private SingleServiceTracker<RegionsPersistence> regionsTracker;
+ private StandardRegionDigraph digraph;
+ private StandardManageableRegionDigraph digraphMBean;
public Activator() {
// Special case here, as we don't want the activator to wait for current job to finish,
@@ -87,32 +99,28 @@ public class Activator extends BaseActivator {
return;
}
+ // Resolver
+ register(Resolver.class, new ResolverImpl(new Slf4jResolverLog(LoggerFactory.getLogger(ResolverImpl.class))));
+
+ // RegionDigraph
+ digraph = DigraphHelper.loadDigraph(bundleContext);
+ register(ResolverHookFactory.class, digraph.getResolverHookFactory());
+ register(CollisionHook.class, DigraphHelper.getCollisionHook(digraph));
+ register(org.osgi.framework.hooks.bundle.FindHook.class, digraph.getBundleFindHook());
+ register(org.osgi.framework.hooks.bundle.EventHook.class, digraph.getBundleEventHook());
+ register(org.osgi.framework.hooks.service.FindHook.class, digraph.getServiceFindHook());
+ register(org.osgi.framework.hooks.service.EventHook.class, digraph.getServiceEventHook());
+ register(RegionDigraph.class, digraph);
+ digraphMBean = new StandardManageableRegionDigraph(digraph, "org.apache.karaf", bundleContext);
+ digraphMBean.registerMBean();
+
+
FeatureFinder featureFinder = new FeatureFinder();
Hashtable<String, Object> props = new Hashtable<String, Object>();
props.put(Constants.SERVICE_PID, FEATURES_REPOS_PID);
register(ManagedService.class, featureFinder, props);
- // TODO: region support
-// final BundleManager bundleManager = new BundleManager(bundleContext);
-// regionsTracker = new SingleServiceTracker<RegionsPersistence>(bundleContext, RegionsPersistence.class,
-// new SingleServiceTracker.SingleServiceListener() {
-// @Override
-// public void serviceFound() {
-// bundleManager.setRegionsPersistence(regionsTracker.getService());
-// }
-// @Override
-// public void serviceLost() {
-// serviceFound();
-// }
-// @Override
-// public void serviceReplaced() {
-// serviceFound();
-// }
-// });
-// regionsTracker.open();
-
-
- FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
+ FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
// TODO: honor respectStartLvlDuringFeatureStartup and respectStartLvlDuringFeatureUninstall
// boolean respectStartLvlDuringFeatureStartup = getBoolean("respectStartLvlDuringFeatureStartup", true);
// boolean respectStartLvlDuringFeatureUninstall = getBoolean("respectStartLvlDuringFeatureUninstall", true);
@@ -123,7 +131,7 @@ public class Activator extends BaseActivator {
StateStorage stateStorage = new StateStorage() {
@Override
protected InputStream getInputStream() throws IOException {
- File file = bundleContext.getDataFile("FeaturesServiceState.properties");
+ File file = bundleContext.getDataFile(STATE_FILE);
if (file.exists()) {
return new FileInputStream(file);
} else {
@@ -133,7 +141,7 @@ public class Activator extends BaseActivator {
@Override
protected OutputStream getOutputStream() throws IOException {
- File file = bundleContext.getDataFile("FeaturesServiceState.properties");
+ File file = bundleContext.getDataFile(STATE_FILE);
return new FileOutputStream(file);
}
};
@@ -150,6 +158,7 @@ public class Activator extends BaseActivator {
featureFinder,
eventAdminListener,
configInstaller,
+ digraph,
overrides,
featureResolutionRange,
bundleUpdateRange,
@@ -191,9 +200,9 @@ public class Activator extends BaseActivator {
}
protected void doStop() {
- if (regionsTracker != null) {
- regionsTracker.close();
- regionsTracker = null;
+ if (digraphMBean != null) {
+ digraphMBean.unregisterMbean();
+ digraphMBean = null;
}
if (featuresListenerTracker != null) {
featuresListenerTracker.close();
@@ -203,6 +212,14 @@ public class Activator extends BaseActivator {
if (featuresService != null) {
featuresService = null;
}
+ if (digraph != null) {
+ try {
+ DigraphHelper.saveDigraph(bundleContext, digraph);
+ } catch (Exception e) {
+ // Ignore
+ }
+ digraph = null;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/karaf/blob/2705ad88/features/core/src/main/java/org/apache/karaf/features/internal/region/AbstractRegionDigraphVisitor.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/region/AbstractRegionDigraphVisitor.java b/features/core/src/main/java/org/apache/karaf/features/internal/region/AbstractRegionDigraphVisitor.java
new file mode 100644
index 0000000..3ad2325
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/region/AbstractRegionDigraphVisitor.java
@@ -0,0 +1,121 @@
+/*
+ * 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.internal.region;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.equinox.region.Region;
+import org.eclipse.equinox.region.RegionDigraphVisitor;
+import org.eclipse.equinox.region.RegionFilter;
+
+/**
+ * {@link AbstractRegionDigraphVisitor} is an abstract base class for {@link RegionDigraphVisitor} implementations
+ */
+public abstract class AbstractRegionDigraphVisitor<C> implements RegionDigraphVisitor {
+
+ private final Collection<C> allCandidates;
+ private final Deque<Set<C>> allowedDeque = new ArrayDeque<Set<C>>();
+ private final Deque<Collection<C>> filteredDeque = new ArrayDeque<Collection<C>>();
+ private Set<C> allowed = new HashSet<C>();
+
+ public AbstractRegionDigraphVisitor(Collection<C> candidates) {
+ this.allCandidates = candidates;
+ }
+
+ public Collection<C> getAllowed() {
+ return allowed;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean visit(Region region) {
+ Collection<C> candidates = filteredDeque.isEmpty() ? allCandidates : filteredDeque.peek();
+ for (C candidate : candidates) {
+ if (contains(region, candidate)) {
+ allowed.add(candidate);
+ }
+ }
+ // there is no need to traverse edges of this region,
+ // it contains all the remaining filtered candidates
+ return !allowed.containsAll(candidates);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean preEdgeTraverse(RegionFilter regionFilter) {
+ // Find the candidates filtered by the previous edge
+ Collection<C> filtered = filteredDeque.isEmpty() ? allCandidates : filteredDeque.peek();
+ Collection<C> candidates = new ArrayList<C>(filtered);
+ // remove any candidates contained in the current region
+ candidates.removeAll(allowed);
+ // apply the filter across remaining candidates
+ Iterator<C> i = candidates.iterator();
+ while (i.hasNext()) {
+ C candidate = i.next();
+ if (!isAllowed(candidate, regionFilter)) {
+ i.remove();
+ }
+ }
+ if (candidates.isEmpty())
+ return false; // this filter does not apply; avoid traversing this edge
+ // push the filtered candidates for the next region
+ filteredDeque.push(candidates);
+ // push the allowed
+ allowedDeque.push(allowed);
+ allowed = new HashSet<C>();
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void postEdgeTraverse(RegionFilter regionFilter) {
+ filteredDeque.poll();
+ Collection<C> candidates = allowed;
+ allowed = allowedDeque.pop();
+ allowed.addAll(candidates);
+ }
+
+ /**
+ * Determines whether the given region contains the given candidate.
+ *
+ * @param region the {@link Region}
+ * @param candidate the candidate
+ * @return <code>true</code> if and only if the given region contains the given candidate
+ */
+ protected abstract boolean contains(Region region, C candidate);
+
+ /**
+ * Determines whether the given candidate is allowed by the given {@link RegionFilter}.
+ *
+ * @param candidate the candidate
+ * @param filter the filter
+ * @return <code>true</code> if and only if the given candidate is allowed by the given filter
+ */
+ protected abstract boolean isAllowed(C candidate, RegionFilter filter);
+}
\ No newline at end of file