You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/11/07 11:45:21 UTC
[sling-org-apache-sling-feature-apiregions] 01/19: Rename feature
whitelist component to feature apiregions
This is an automated email from the ASF dual-hosted git repository.
davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-apiregions.git
commit f8b59082f5dc4bf4be75ba75c64b976317cb9ae8
Author: David Bosschaert <bo...@adobe.com>
AuthorDate: Sat Nov 3 10:11:24 2018 +0000
Rename feature whitelist component to feature apiregions
---
pom.xml | 125 ++++++++++++
.../sling/feature/apiregions/impl/Activator.java | 35 ++++
.../feature/apiregions/impl/RegionEnforcer.java | 134 +++++++++++++
.../feature/apiregions/impl/ResolverHookImpl.java | 186 ++++++++++++++++++
.../apiregions/impl/ResolverHookImplTest.java | 209 +++++++++++++++++++++
.../apiregions/impl/WhitelistEnforcerTest.java | 62 ++++++
.../impl/WhitelistServiceFactoryImplTest.java | 64 +++++++
.../apiregions/impl/WhitelistServiceImplTest.java | 52 +++++
8 files changed, 867 insertions(+)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5602b63
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+ <!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>34</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>org.apache.sling.feature.apiregions</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Feature API Regions Runtime</name>
+ <description>
+ A runtime component to enforce API Regions
+ </description>
+
+ <properties>
+ <sling.java.version>8</sling.java.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <ExtensionBundle-Activator>org.apache.sling.feature.apiregions.impl.Activator</ExtensionBundle-Activator>
+ <Fragment-Host>system.bundle;extension:=framework</Fragment-Host>
+ </instructions>
+
+ <!-- Skip baselining for 0.x version -->
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>*.md</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.feature.service</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.1_spec</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.feature</artifactId>
+ <version>0.1.3-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.feature.launcher</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+
+ <!-- Testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.8.9</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-core</artifactId>
+ <version>1.0.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java b/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java
new file mode 100644
index 0000000..7d77d5e
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/Activator.java
@@ -0,0 +1,35 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+
+public class Activator implements BundleActivator {
+ @Override
+ public synchronized void start(BundleContext context) throws Exception {
+ RegionEnforcer enforcer = new RegionEnforcer();
+ context.registerService(ResolverHookFactory.class, enforcer, null);
+ }
+
+ @Override
+ public synchronized void stop(BundleContext context) throws Exception {
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/RegionEnforcer.java b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionEnforcer.java
new file mode 100644
index 0000000..26807ef
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionEnforcer.java
@@ -0,0 +1,134 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+import org.osgi.framework.Version;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+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.Properties;
+import java.util.Set;
+
+class RegionEnforcer implements ResolverHookFactory {
+ private static final String idbsnverFileName = "idbsnver.properties";
+ private static final String bundleFeatureFileName = "bundles.properties";
+ private static final String regionPackageFileName = "regions.properties";
+ private static final String featureRegionFileName = "features.properties";
+
+ private final Map<Map.Entry<String, Version>, List<String>> bsnVerMap;
+ private final Map<String, Set<String>> bundleFeatureMap;
+ private final Map<String, Set<String>> featureRegionMap;
+ private final Map<String, Set<String>> regionPackageMap;
+
+ public RegionEnforcer() throws IOException {
+ bsnVerMap = populateBSNVerMap();
+ bundleFeatureMap = populateBundleFeatureMap();
+ featureRegionMap = populateFeatureRegionMap();
+ regionPackageMap = populateRegionPackageMap();
+ }
+
+ private Map<Map.Entry<String, Version>, List<String>> populateBSNVerMap() throws IOException {
+ File idbsnverFile = getDataFile(idbsnverFileName);
+ if (idbsnverFile != null && idbsnverFile.exists()) {
+ Map<Map.Entry<String, Version>, List<String>> m = new HashMap<>();
+
+ Properties p = new Properties();
+ try (InputStream is = new FileInputStream(idbsnverFile)) {
+ p.load(is);
+ }
+
+ for (String n : p.stringPropertyNames()) {
+ String[] bsnver = p.getProperty(n).split("~");
+ Map.Entry<String, Version> key = new AbstractMap.SimpleEntry<String, Version>(bsnver[0], Version.valueOf(bsnver[1]));
+ List<String> l = m.get(key);
+ if (l == null) {
+ l = new ArrayList<>();
+ m.put(key, l);
+ }
+ }
+
+ Map<Map.Entry<String, Version>, List<String>> m2 = new HashMap<>();
+
+ for (Map.Entry<Map.Entry<String, Version>, List<String>> entry : m.entrySet()) {
+ m2.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
+ }
+
+ return Collections.unmodifiableMap(m2);
+ } else {
+ return Collections.emptyMap();
+ }
+ }
+
+ private Map<String, Set<String>> populateBundleFeatureMap() throws IOException {
+ return loadMap(bundleFeatureFileName);
+ }
+
+ private Map<String, Set<String>> populateFeatureRegionMap() throws IOException {
+ return loadMap(featureRegionFileName);
+ }
+
+ private Map<String, Set<String>> populateRegionPackageMap() throws IOException {
+ return loadMap(regionPackageFileName);
+ }
+
+ private Map<String, Set<String>> loadMap(String fileName) throws IOException {
+ Map<String, Set<String>> m = new HashMap<>();
+
+ File propsFile = getDataFile(fileName);
+ if (propsFile != null && propsFile.exists()) {
+ Properties p = new Properties();
+ try (InputStream is = new FileInputStream(propsFile)) {
+ p.load(is);
+ }
+
+ for (String n : p.stringPropertyNames()) {
+ String[] features = p.getProperty(n).split(",");
+ m.put(n, Collections.unmodifiableSet(new HashSet<>(Arrays.asList(features))));
+ }
+ }
+
+ return Collections.unmodifiableMap(m);
+ }
+
+ private File getDataFile(String name) throws IOException {
+ String fn = System.getProperty("whitelisting." + name);
+ if (fn == null)
+ return null;
+ return new File(fn);
+ }
+
+ @Override
+ public ResolverHook begin(Collection<BundleRevision> triggers) {
+ return new ResolverHookImpl(bsnVerMap, bundleFeatureMap, featureRegionMap, regionPackageMap);
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java b/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java
new file mode 100644
index 0000000..5642d47
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java
@@ -0,0 +1,186 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+class ResolverHookImpl implements ResolverHook {
+ private static final Logger LOG = Logger.getLogger(ResolverHookImpl.class.getName());
+
+ private final Map<Map.Entry<String, Version>, List<String>> bsnVerMap;
+ private final Map<String, Set<String>> bundleFeatureMap;
+ private final Map<String, Set<String>> featureRegionMap;
+ private final Map<String, Set<String>> regionPackageMap;
+
+ public ResolverHookImpl(Map<Entry<String, Version>, List<String>> bsnVerMap, Map<String, Set<String>> bundleFeatureMap,
+ Map<String, Set<String>> featureRegionMap, Map<String, Set<String>> regionPackageMap) {
+ this.bsnVerMap = bsnVerMap;
+ this.bundleFeatureMap = bundleFeatureMap;
+ this.featureRegionMap = featureRegionMap;
+ this.regionPackageMap = regionPackageMap;
+ }
+
+ @Override
+ public void filterResolvable(Collection<BundleRevision> candidates) {
+ // Nothing to do
+ }
+
+ @Override
+ public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {
+ // Nothing to do
+ }
+
+ @Override
+ public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
+ // Filtering is only on package resolution. Any other kind of resolution is not limited
+ if (!PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace()))
+ return;
+
+ System.out.println("*** Filter Matches: " + requirement);
+ Bundle reqBundle = requirement.getRevision().getBundle();
+ long reqBundleID = reqBundle.getBundleId();
+ String reqBundleName = reqBundle.getSymbolicName();
+ Version reqBundleVersion = reqBundle.getVersion();
+
+ List<String> aids = bsnVerMap.get(new AbstractMap.SimpleEntry<String, Version>(reqBundleName, reqBundleVersion));
+ if (aids == null)
+ return; // TODO what to do?
+ List<String> reqFeatures = new ArrayList<>();
+ for (String aid : aids) {
+ Set<String> fid = bundleFeatureMap.get(aid);
+ if (fid != null)
+ reqFeatures.addAll(fid);
+ }
+
+ Set<String> regions = new HashSet<>();
+ for (String feature : reqFeatures) {
+ Set<String> fr = featureRegionMap.get(feature);
+ if (fr != null) {
+ regions.addAll(fr);
+ }
+ }
+
+ Set<BundleCapability> coveredCaps = new HashSet<>();
+
+ nextCapability:
+ for (BundleCapability bc : candidates) {
+ BundleRevision rev = bc.getRevision();
+
+ Bundle capBundle = rev.getBundle();
+ long capBundleID = capBundle.getBundleId();
+ if (capBundleID == 0) {
+ // always allow capability from the system bundle
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+
+ if (capBundleID == reqBundleID) {
+ // always allow capability from same bundle
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+
+ String capBundleName = capBundle.getSymbolicName();
+ Version capBundleVersion = capBundle.getVersion();
+
+ List<String> capBundleArtifacts = bsnVerMap.get(new AbstractMap.SimpleEntry<String, Version>(capBundleName, capBundleVersion));
+ if (capBundleArtifacts == null)
+ return; // TODO what to do?
+ List<String> capFeatures = new ArrayList<>();
+ for (String ba : capBundleArtifacts) {
+ Set<String> capfeats = bundleFeatureMap.get(ba);
+ if (capfeats != null)
+ capFeatures.addAll(capfeats);
+ }
+
+ if (capFeatures.isEmpty())
+ capFeatures = Collections.singletonList(null);
+
+ for (String capFeat : capFeatures) {
+ if (capFeat == null) {
+ // always allow capability not coming from a feature
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+
+ if (reqFeatures.contains(capFeat)) {
+ // Within a single feature everything can wire to everything else
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+
+ if (featureRegionMap.get(capFeat) == null) {
+ // If the feature hosting the capability has no regions defined, everyone can access
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+
+ Object pkg = bc.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ if (pkg instanceof String) {
+ String packageName = (String) pkg;
+
+ Set<String> globalPackages = regionPackageMap.get("global");
+ if (globalPackages != null && globalPackages.contains(packageName)) {
+ // If the export is in the global region everyone can access
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+
+ for (String region : regions) {
+ Set<String> regionPackages = regionPackageMap.get(region);
+ if (regionPackages != null && regionPackages.contains(packageName)) {
+ // If the export is in a region that the feature is also in, then allow
+ coveredCaps.add(bc);
+ continue nextCapability;
+ }
+ }
+ }
+ }
+ }
+
+ // Remove any capabilities that are not covered
+ if (candidates.retainAll(coveredCaps)) {
+ LOG.log(Level.INFO,
+ "Removed one ore more candidates for requirement {0} as they are not in the correct region", requirement);
+ }
+ }
+
+ @Override
+ public void end() {
+ // Nothing to do
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java
new file mode 100644
index 0000000..5bc60a1
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class ResolverHookImplTest {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Test @Ignore
+ public void testFilterMatches() throws Exception {
+ String f = "gid:aid:0.0.9";
+ String f2 = "gid2:aid2:1.0.0-SNAPSHOT";
+ String f3 = "gid3:aid3:1.2.3";
+ String f4 = "gid4:aid4:1.2.3";
+ String f5 = "gid5:aid5:1.2.3";
+
+ /*
+ Features fs = Mockito.mock(Features.class);
+ Mockito.when(fs.getFeaturesForBundle("a.b.c", new Version(0,0,0)))
+ .thenReturn(Collections.singleton(f)); // b7
+ Mockito.when(fs.getFeaturesForBundle("some.other.bundle", new Version(9,9,9,"suffix")))
+ .thenReturn(Collections.singleton(f2)); // b9
+ Mockito.when(fs.getFeaturesForBundle("a-bundle", new Version(1,0,0,"SNAPSHOT")))
+ .thenReturn(Collections.singleton(f2)); // b10
+ Mockito.when(fs.getFeaturesForBundle("a.b.c", new Version(1,2,3)))
+ .thenReturn(Collections.singleton(f3)); // b17
+ Mockito.when(fs.getFeaturesForBundle("z.z.z", new Version(3,2,1)))
+ .thenReturn(new HashSet<>(Arrays.asList(f, f3))); // b18
+ Mockito.when(fs.getFeaturesForBundle("x.y.z", new Version(9,9,9)))
+ .thenReturn(Collections.singleton(f3)); // b19
+ Mockito.when(fs.getFeaturesForBundle("zzz", new Version(1,0,0)))
+ .thenReturn(Collections.singleton(f4)); // b20
+ Mockito.when(fs.getFeaturesForBundle("www", new Version(1,0,0)))
+ .thenReturn(Collections.singleton(f5)); // b20
+
+ ServiceTracker st = Mockito.mock(ServiceTracker.class);
+ Mockito.when(st.waitForService(Mockito.anyLong())).thenReturn(fs);
+
+ Map<String, Set<String>> rpm = new HashMap<>();
+ rpm.put("r0", Collections.singleton("org.bar"));
+ rpm.put("r1", new HashSet<>(Arrays.asList("org.blah", "org.foo")));
+ rpm.put(WhitelistService.GLOBAL_REGION, Collections.singleton("org.bar.tar"));
+ rpm.put("r3", Collections.singleton("xyz"));
+
+ Map<String, Set<String>> frm = new HashMap<>();
+ frm.put("gid:aid:0.0.9",
+ new HashSet<>(Arrays.asList("r1", "r2", WhitelistService.GLOBAL_REGION)));
+ frm.put("gid2:aid2:1.0.0-SNAPSHOT", Collections.singleton("r2"));
+ frm.put("gid3:aid3:1.2.3", Collections.singleton("r3"));
+ frm.put("gid4:aid4:1.2.3", Collections.singleton("r3"));
+ frm.put("gid5:aid5:1.2.3", Collections.emptySet());
+
+ WhitelistService wls = new WhitelistServiceImpl(rpm, frm);
+ ResolverHookImpl rh = new ResolverHookImpl(st, wls);
+
+ // Check that we can get the capability from another bundle in the same region
+ // Bundle 7 is in feature f with regions r1, r2. Bundle 9 is in feature f2 with regions r2
+ BundleRequirement req = mockRequirement(7, "a.b.c", new Version(0,0,0));
+ BundleCapability bc1 = mockCapability("org.foo", 9, "some.other.bundle", new Version(9,9,9,"suffix"));
+ List<BundleCapability> candidates = new ArrayList<>(Arrays.asList(bc1));
+ rh.filterMatches(req, candidates);
+ assertEquals(Collections.singletonList(bc1), candidates);
+
+ // Check that we cannot get the capability from another bundle in a different region
+ // Bundle 9 is in feature f2 with region r2
+ BundleRequirement req2 = mockRequirement(9, "some.other.bundle", new Version(9,9,9,"suffix"));
+ BundleCapability bc2 = mockCapability("org.bar", 17, "a.b.c", new Version(1,2,3));
+ Collection<BundleCapability> c2 = new ArrayList<>(Arrays.asList(bc2));
+ rh.filterMatches(req2, c2);
+ assertEquals(0, c2.size());
+
+ // Check that we can get the capability from the same bundle
+ BundleRequirement req3 = mockRequirement(9, "some.other.bundle", new Version(9,9,9,"suffix"));
+ BundleCapability bc3 = mockCapability("org.bar", 9, "some.other.bundle", new Version(9,9,9,"suffix"));
+ Collection<BundleCapability> c3 = new ArrayList<>(Arrays.asList(bc3));
+ rh.filterMatches(req3, c3);
+ assertEquals(Collections.singletonList(bc3), c3);
+
+ // Check that we can get the capability from the another bundle in the same feature
+ BundleRequirement req4 = mockRequirement(9, "some.other.bundle", new Version(9,9,9,"suffix"));
+ BundleCapability bc4 = mockCapability("org.bar", 10, "a-bundle", new Version(1,0,0,"SNAPSHOT"));
+ Collection<BundleCapability> c4 = new ArrayList<>(Arrays.asList(bc4));
+ rh.filterMatches(req4, c4);
+ assertEquals(Collections.singletonList(bc4), c4);
+
+ // Check that we can get the capability from another bundle where the capability
+ // is globally visible (from bundle 9, f2)
+ BundleRequirement req5 = mockRequirement(17, "a.b.c", new Version(1,2,3));
+ BundleCapability bc5 = mockCapability("org.bar.tar", 9, "some.other.bundle", new Version(9,9,9,"suffix"));
+ Collection<BundleCapability> c5 = new ArrayList<>(Arrays.asList(bc5));
+ rh.filterMatches(req5, c5);
+ assertEquals(Collections.singletonList(bc5), c5);
+
+ // Check that we can get the capability from another bundle where the capability
+ // is globally visible (from bundle 7, f)
+ BundleRequirement req6 = mockRequirement(7, "a.b.c", new Version(0,0,0));
+ BundleCapability bc6 = mockCapability("org.bar.tar", 17, "a.b.c", new Version(1,2,3));
+ Collection<BundleCapability> c6 = new ArrayList<>(Arrays.asList(bc6));
+ rh.filterMatches(req6, c6);
+ assertEquals(Collections.singletonList(bc6), c6);
+
+ // Check that capabilities in non-package namespaces are ignored
+ BundleRequirement req7 = Mockito.mock(BundleRequirement.class);
+ Mockito.when(req7.getNamespace()).thenReturn("some.other.namespace");
+ BundleCapability bc7 = mockCapability("org.bar", 17, "a.b.c", new Version(1,2,3));
+ Collection<BundleCapability> c7 = new ArrayList<>(Arrays.asList(bc7));
+ rh.filterMatches(req7, c7);
+ assertEquals(Collections.singletonList(bc7), c7);
+
+ // Check that we can get the capability from another provider in the same region
+ BundleRequirement req8 = mockRequirement(20, "zzz", new Version(1,0,0));
+ BundleCapability bc8 = mockCapability("xyz", 19, "x.y.z", new Version(9,9,9));
+ Collection<BundleCapability> c8 = new ArrayList<>(Arrays.asList(bc8));
+ rh.filterMatches(req8, c8);
+
+ assertEquals(Collections.singletonList(bc8), c8);
+ // A requirement from a bundle that has no feature cannot access one in a region
+ BundleRequirement req9 = mockRequirement(11, "qqq", new Version(6,6,6));
+ BundleCapability bc9 = mockCapability("org.bar", 17, "a.b.c", new Version(1,2,3));
+ ArrayList c9 = new ArrayList<>(Arrays.asList(bc9));
+ rh.filterMatches(req9, c9);
+ assertEquals(0, c9.size());
+
+ // A requirement from a bundle that has no feature can still access on in the global region
+ BundleRequirement req10 = mockRequirement(11, "qqq", new Version(6,6,6));
+ BundleCapability bc10 = mockCapability("org.bar.tar", 18, "z.z.z", new Version(3,2,1));
+ ArrayList c10 = new ArrayList<>(Arrays.asList(bc10));
+ rh.filterMatches(req10, c10);
+ assertEquals(Collections.singletonList(bc10), c10);
+
+ // A requirement from a bundle that has no feature can be satisfied by a capability
+ // from a bundle that has no feature
+ BundleRequirement req11 = mockRequirement(11, "qqq", new Version(6,6,6));
+ BundleCapability bc11 = mockCapability("org.bar.tar", 20, "www", new Version(1,0,0));
+ ArrayList c11 = new ArrayList<>(Arrays.asList(bc11));
+ rh.filterMatches(req11, c11);
+ assertEquals(Collections.singletonList(bc11), c11);
+
+ // A capability from the system bundle is always accessible
+ BundleRequirement req12 = mockRequirement(11, "qqq", new Version(6,6,6));
+ BundleCapability bc12 = mockCapability("ping.pong", 0, "system.bundle", new Version(3,2,1));
+ ArrayList c12 = new ArrayList<>(Arrays.asList(bc12));
+ rh.filterMatches(req12, c12);
+ assertEquals(Collections.singletonList(bc12), c12);
+ */
+ }
+
+ private BundleCapability mockCapability(String pkg, long bundleID, String bsn, Version version) {
+ Map<String, Object> attrs =
+ Collections.singletonMap(PackageNamespace.PACKAGE_NAMESPACE, pkg);
+
+ Bundle bundle = Mockito.mock(Bundle.class);
+ Mockito.when(bundle.getBundleId()).thenReturn(bundleID);
+ Mockito.when(bundle.getSymbolicName()).thenReturn(bsn);
+ Mockito.when(bundle.getVersion()).thenReturn(version);
+
+ BundleRevision br = Mockito.mock(BundleRevision.class);
+ Mockito.when(br.getBundle()).thenReturn(bundle);
+
+
+ BundleCapability cap = Mockito.mock(BundleCapability.class);
+ Mockito.when(cap.getAttributes()).thenReturn(attrs);
+ Mockito.when(cap.getRevision()).thenReturn(br);
+ return cap;
+ }
+
+ private BundleRequirement mockRequirement(long bundleID, String bsn, Version version) {
+ Bundle bundle = Mockito.mock(Bundle.class);
+ Mockito.when(bundle.getBundleId()).thenReturn(bundleID);
+ Mockito.when(bundle.getSymbolicName()).thenReturn(bsn);
+ Mockito.when(bundle.getVersion()).thenReturn(version);
+
+ BundleRevision br = Mockito.mock(BundleRevision.class);
+ Mockito.when(br.getBundle()).thenReturn(bundle);
+
+ BundleRequirement req = Mockito.mock(BundleRequirement.class);
+ Mockito.when(req.getNamespace()).thenReturn(PackageNamespace.PACKAGE_NAMESPACE);
+ Mockito.when(req.getRevision()).thenReturn(br);
+
+ return req;
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistEnforcerTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistEnforcerTest.java
new file mode 100644
index 0000000..e83c27f
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistEnforcerTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+public class WhitelistEnforcerTest {
+ /*
+ @Test
+ public void testWhitelistEnforcerConfigUpdate() throws ConfigurationException {
+ BundleContext bc = Mockito.mock(BundleContext.class);
+ WhitelistEnforcer enf = new WhitelistEnforcer(bc, null);
+
+ assertNull("Precondition", enf.whitelistService);
+
+ Dictionary<String, Object> props = new Hashtable<>();
+ props.put("ignored", "ignored-too");
+ props.put("whitelist.region.region1", "org.foo.pkg1");
+ props.put("whitelist.region.region2", new String[] {"pkga", "pkgb"});
+ props.put("whitelist.region.region.3", Arrays.asList("a.b.c", "d.e.f", "g.h.i"));
+ props.put("whitelist.feature.gid:aid:slingfeature:testfeature:1.0.0", WhitelistService.GLOBAL_REGION);
+ props.put("whitelist.feature.gid:myfeature:1.0.0", new String [] {"region1", "region2"});
+ enf.updated(props);
+
+ assertNotNull(enf.whitelistService);
+ Mockito.verify(bc, Mockito.times(1)).registerService(
+ Mockito.eq(WhitelistService.class), Mockito.isA(WhitelistService.class), Mockito.any());
+
+ // check that the configuration parsing worked
+ assertTrue(enf.whitelistService.regionWhitelistsPackage("region1", "org.foo.pkg1"));
+ assertTrue(enf.whitelistService.regionWhitelistsPackage("region2", "pkga"));
+ assertTrue(enf.whitelistService.regionWhitelistsPackage("region2", "pkgb"));
+ assertFalse(enf.whitelistService.regionWhitelistsPackage("region1", "pkg1"));
+ assertTrue(enf.whitelistService.regionWhitelistsPackage("region.3", "d.e.f"));
+ assertFalse(enf.whitelistService.regionWhitelistsPackage("region.3", "d.e.f.g"));
+ assertNull(enf.whitelistService.regionWhitelistsPackage("unknown", "pkga"));
+
+ Set<String> regions = enf.whitelistService.listRegions("gid:myfeature:1.0.0");
+ assertEquals(new HashSet<String>(Arrays.asList("region1", "region2")), regions);
+ assertEquals(Collections.singleton("global"), enf.whitelistService
+ .listRegions("gid:aid:slingfeature:testfeature:1.0.0"));
+ assertNull(enf.whitelistService.listRegions("unknown"));
+
+ enf.updated(null);
+ assertNull("A null configuration should put back the null whitelist service", enf.whitelistService);
+ }
+ */
+}
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceFactoryImplTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceFactoryImplTest.java
new file mode 100644
index 0000000..f9120d3
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceFactoryImplTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+public class WhitelistServiceFactoryImplTest {
+ /*
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Test
+ public void testWhitelistServiceFactory() {
+ List<ResolverHookFactory> resolverHookFactory = new ArrayList<>();
+ Map<String, Map<String, Set<String>>> wlsCfg = new HashMap<>();
+
+ ServiceTracker st = Mockito.mock(ServiceTracker.class);
+ BundleContext bc = Mockito.mock(BundleContext.class);
+ Mockito.when(bc.registerService(Mockito.isA(Class.class), Mockito.isA(Object.class), Mockito.isA(Dictionary.class)))
+ .then(i -> { resolverHookFactory.add(i.getArgument(1)); return null; });
+
+ WhitelistServiceFactory wsf = new WhitelistServiceFactoryImpl(bc, st) {
+ @Override
+ WhitelistService createWhitelistService(Map<String, Set<String>> packages, Map<String, Set<String>> regions) {
+ wlsCfg.put("packages", packages);
+ wlsCfg.put("regions", regions);
+ return super.createWhitelistService(packages, regions);
+ }
+ };
+
+ Map<String, Map<String, Set<String>>> m = new HashMap<>();
+ Map<String, Set<String>> packages = new HashMap<>();
+ packages.put("region1", new HashSet<>(Arrays.asList("org.foo", "org.bar")));
+ packages.put("region2", Collections.singleton("org.foo.bar"));
+ m.put("packages", packages);
+
+ Map<String, Set<String>> regions = new HashMap<>();
+ regions.put("f1", new HashSet<String>(Arrays.asList("region1", "region3")));
+ regions.put("f2", Collections.singleton("region2"));
+ regions.put("f3", Collections.singleton("region4"));
+ regions.put("f4", Collections.singleton("region2"));
+ m.put("regions", regions);
+
+ wsf.initialize(m);
+
+ assertEquals(wlsCfg, m);
+
+ ResolverHookFactory rhf = resolverHookFactory.get(0);
+ assertTrue(rhf instanceof WhitelistEnforcer);
+ }
+ */
+}
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceImplTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceImplTest.java
new file mode 100644
index 0000000..832e199
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/WhitelistServiceImplTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sling.feature.apiregions.impl;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class WhitelistServiceImplTest {
+ @Test
+ @Ignore
+ public void testListRegions() {
+ /*
+ Map<String, Set<String>> frm = new HashMap<>();
+ frm.put("myfeature", new HashSet<>(Arrays.asList("rega", "regb", "regc")));
+ frm.put("myotherfeature", Collections.emptySet());
+ WhitelistService wls = new WhitelistServiceImpl(Collections.emptyMap(), frm);
+
+ assertEquals(new HashSet<>(Arrays.asList("rega", "regb", "regc")),
+ wls.listRegions("myfeature"));
+ assertEquals(0, wls.listRegions("myotherfeature").size());
+ assertNull(wls.listRegions("nonexistent"));
+ }
+
+ @Test
+ public void testRegionContainsPackage() {
+ Map<String, Set<String>> rpm = new HashMap<>();
+ rpm.put("region1", new HashSet<>(Arrays.asList("org.foo", "org.bar", "org.foo.bar")));
+ WhitelistService wls = new WhitelistServiceImpl(rpm, Collections.emptyMap());
+
+ assertTrue(wls.regionWhitelistsPackage("region1", "org.foo"));
+ assertTrue(wls.regionWhitelistsPackage("region1", "org.foo.bar"));
+ assertFalse(wls.regionWhitelistsPackage("region1", "org.bar.foo"));
+ assertNull(wls.regionWhitelistsPackage("nonexitent", "org.foo"));
+ */
+ }
+}