Merge pull request #13 from bosschaert/SLING-9521-sq
SLING-9521 Packages exported in earlier API Regions are not available to later API Regions
diff --git a/src/main/java/org/apache/sling/feature/apiregions/impl/RegionConfiguration.java b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionConfiguration.java
index f0f25e6..acb7720 100644
--- a/src/main/java/org/apache/sling/feature/apiregions/impl/RegionConfiguration.java
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/RegionConfiguration.java
@@ -37,7 +37,6 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -45,6 +44,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.function.Supplier;
import java.util.logging.Level;
import org.osgi.framework.BundleContext;
@@ -52,10 +52,11 @@
class RegionConfiguration {
private static final String BUNDLE_LOCATION_TO_FEATURE_FILE = "bundleLocationToFeature.properties";
+ private static final String REGION_ORDER = "__region.order__";
volatile Map<Map.Entry<String, Version>, List<String>> bsnVerMap;
volatile Map<String, Set<String>> bundleFeatureMap;
- volatile Map<String, Set<String>> featureRegionMap;
+ volatile Map<String, List<String>> featureRegionMap;
volatile Map<String, Set<String>> regionPackageMap;
final Set<String> defaultRegions;
@@ -65,8 +66,9 @@
private final Map<Map.Entry<String, Version>, List<String>> baseBsnVerMap;
private final Map<String, Set<String>> baseBundleFeatureMap;
- private final Map<String, Set<String>> baseFeatureRegionMap;
+ private final Map<String, List<String>> baseFeatureRegionMap;
private final Map<String, Set<String>> baseRegionPackageMap;
+ private final List<String> globalRegionOrder;
// This field stores the association between bundle location and the configuration
// to be used. The configuration is based on bsn+version. If the bundle is updated
@@ -78,13 +80,15 @@
private final String toGlobalConfig;
RegionConfiguration(Map<Entry<String, Version>, List<String>> bsnVerMap, Map<String, Set<String>> bundleFeatureMap,
- Map<String, Set<String>> featureRegionMap, Map<String, Set<String>> regionPackageMap, Set<String> defaultRegions) {
+ Map<String, List<String>> featureRegionMap, Map<String, Set<String>> regionPackageMap, Set<String> defaultRegions) {
this.defaultRegions = defaultRegions;
this.baseBsnVerMap = new HashMap<>(bsnVerMap);
this.baseBundleFeatureMap = new HashMap<>(bundleFeatureMap);
this.baseFeatureRegionMap = new HashMap<>(featureRegionMap);
this.baseRegionPackageMap = new HashMap<>(regionPackageMap);
+ this.globalRegionOrder = new ArrayList<>(this.baseFeatureRegionMap.getOrDefault(REGION_ORDER, Collections.emptyList()));
+ this.baseFeatureRegionMap.remove(REGION_ORDER);
this.toGlobalConfig = null;
@@ -106,7 +110,7 @@
URI featuresFile = getDataFileURI(context, RegionConstants.FEATURE_REGION_FILENAME);
// Register the location as a service property for diagnostic purposes
regProps.put(RegionConstants.FEATURE_REGION_FILENAME, featuresFile.toString());
- Map<String, Set<String>> frm = populateFeatureRegionMap(featuresFile);
+ Map<String, List<String>> frm = populateFeatureRegionMap(featuresFile);
URI regionsFile = getDataFileURI(context, RegionConstants.REGION_PACKAGE_FILENAME);
// Register the location as a service property for diagnostic purposes
@@ -118,6 +122,8 @@
this.baseBundleFeatureMap = bfm;
this.baseFeatureRegionMap = frm;
this.baseRegionPackageMap = rpm;
+ this.globalRegionOrder = new ArrayList<>(this.baseFeatureRegionMap.getOrDefault(REGION_ORDER, Collections.emptyList()));
+ this.baseFeatureRegionMap.remove(REGION_ORDER);
this.toGlobalConfig = context.getProperty(RegionConstants.APIREGIONS_JOINGLOBAL);
if ( this.toGlobalConfig != null ) {
@@ -206,7 +212,7 @@
private synchronized void updateConfiguration() {
final Map<Entry<String, Version>, List<String>> bvm = cloneMapOfLists(this.baseBsnVerMap);
final Map<String, Set<String>> bfm = cloneMapOfSets(this.baseBundleFeatureMap);
- final Map<String, Set<String>> frm = cloneMapOfSets(this.baseFeatureRegionMap);
+ final Map<String, List<String>> frm = cloneMapOfLists(this.baseFeatureRegionMap);
final Map<String, Set<String>> rpm = cloneMapOfSets(this.baseRegionPackageMap);
// apply configurations
@@ -227,19 +233,19 @@
// bundle id to features
valObj = props.get(RegionConstants.PROP_bundleFeatures);
if ( valObj != null ) {
- handleMapConfig(valObj, bfm);
+ handleMapConfig(valObj, bfm, HashSet::new);
}
// feature id to regions
valObj = props.get(RegionConstants.PROP_featureRegions);
if ( valObj != null ) {
- handleMapConfig(valObj, frm);
+ handleMapConfig(valObj, frm, ArrayList::new);
}
// region to packages
valObj = props.get(RegionConstants.PROP_regionPackage);
if ( valObj != null ) {
- handleMapConfig(valObj, rpm);
+ handleMapConfig(valObj, rpm, HashSet::new);
}
}
@@ -251,16 +257,16 @@
// Make all maps and their contents unmodifiable
bsnVerMap = unmodifiableMapToList(bvm);
bundleFeatureMap = unmodifiableMapToSet(bfm);
- featureRegionMap = unmodifiableMapToSet(frm);
+ featureRegionMap = unmodifiableMapToList(frm);
regionPackageMap = unmodifiableMapToSet(rpm);
}
- private void handleMapConfig(Object valObj, Map<String, Set<String>> map) {
+ private <T extends Collection<String>> void handleMapConfig(Object valObj, Map<String, T> map, Supplier<T> constructor) {
for(final String val : convert(valObj)) {
final String[] parts = val.split("=");
final String n = parts[0];
final String[] features = parts[1].split(",");
- addValuesToMap(map, n, Arrays.asList(features));
+ addValuesToMap(map, n, Arrays.asList(features), constructor);
}
}
@@ -275,7 +281,7 @@
private static <K,V> Map<K, Set<V>> cloneMapOfSets(Map<K, Set<V>> m) {
final Map<K, Set<V>> newMap = new HashMap<>();
for (Map.Entry<K, Set<V>> entry : m.entrySet()) {
- newMap.put(entry.getKey(), new LinkedHashSet<>(entry.getValue()));
+ newMap.put(entry.getKey(), new HashSet<>(entry.getValue()));
}
return newMap;
}
@@ -300,7 +306,7 @@
if (packages == null)
continue;
- addValuesToMap(rpm, RegionConstants.GLOBAL_REGION, packages);
+ addValuesToMap(rpm, RegionConstants.GLOBAL_REGION, packages, HashSet::new);
rpm.remove(region);
}
}
@@ -337,19 +343,19 @@
}
private static Map<String, Set<String>> populateBundleFeatureMap(URI bundlesFile) throws IOException {
- return loadMap(bundlesFile);
+ return loadMap(bundlesFile, HashSet::new);
}
- private static Map<String, Set<String>> populateFeatureRegionMap(URI featuresFile) throws IOException {
- return loadMap(featuresFile);
+ private static Map<String, List<String>> populateFeatureRegionMap(URI featuresFile) throws IOException {
+ return loadMap(featuresFile, ArrayList::new);
}
private static Map<String, Set<String>> populateRegionPackageMap(URI regionsFile) throws IOException {
- return loadMap(regionsFile);
+ return loadMap(regionsFile, HashSet::new);
}
- private static Map<String, Set<String>> loadMap(URI propsFile) throws IOException {
- Map<String, Set<String>> m = new HashMap<>();
+ private static <T extends Collection<String>> Map<String, T> loadMap(URI propsFile, Supplier<T> constructor) throws IOException {
+ Map<String, T> m = new HashMap<>();
Properties p = new Properties();
try (InputStream is = propsFile.toURL().openStream()) {
@@ -358,16 +364,16 @@
for (String n : p.stringPropertyNames()) {
String[] values = p.getProperty(n).split(",");
- addValuesToMap(m, n, Arrays.asList(values));
+ addValuesToMap(m, n, Arrays.asList(values), constructor);
}
return m;
}
- private static void addValuesToMap(Map<String, Set<String>> map, String key, Collection<String> values) {
- Set<String> bf = map.get(key);
+ private static <T extends Collection<String>> void addValuesToMap(Map<String, T> map, String key, Collection<String> values, Supplier<T> constructor) {
+ T bf = map.get(key);
if (bf == null) {
- bf = new LinkedHashSet<>(); // It's important that the insertion order is maintained.
+ bf = constructor.get();
map.put(key, bf);
}
bf.addAll(values);
@@ -428,7 +434,7 @@
return bundleFeatureMap;
}
- public Map<String, Set<String>> getFeatureRegionMap() {
+ public Map<String, List<String>> getFeatureRegionMap() {
return featureRegionMap;
}
@@ -440,6 +446,10 @@
return defaultRegions;
}
+ public List<String> getGlobalRegionOrder() {
+ return globalRegionOrder;
+ }
+
public Dictionary<String, Object> getRegistrationProperties() {
return regProps;
}
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
index fe5d634..a614851 100644
--- a/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java
+++ b/src/main/java/org/apache/sling/feature/apiregions/impl/ResolverHookImpl.java
@@ -78,7 +78,7 @@
Set<String> bareReqRegions = null; // Null means: not opting into API Regions
Set<String> reqFeatures = getFeaturesForBundle(reqBundle);
for (String feature : reqFeatures) {
- Set<String> fr = this.configuration.getFeatureRegionMap().get(feature);
+ List<String> fr = this.configuration.getFeatureRegionMap().get(feature);
if (fr != null) {
if (bareReqRegions == null)
bareReqRegions = new HashSet<>();
@@ -131,7 +131,7 @@
continue nextCapability;
}
- Set<String> capRegions = this.configuration.getFeatureRegionMap().get(capFeat);
+ List<String> capRegions = this.configuration.getFeatureRegionMap().get(capFeat);
if (capRegions == null || capRegions.size() == 0) {
// If the feature hosting the capability has no regions defined, everyone can access
coveredCaps.put(bc, RegionConstants.GLOBAL_REGION);
@@ -139,7 +139,7 @@
}
bcFeatureMap.put(bc, capFeat);
- List<String> sharedRegions = new ArrayList<>(reqRegions);
+ List<String> sharedRegions = new ArrayList<>(getRegionsAndAncestors(reqRegions));
sharedRegions.retainAll(capRegions);
// Look at specific regions first as they take precedence over the global region
@@ -200,6 +200,28 @@
}
}
+ // Get the a set of the regions plus their ancestors. They are obtained from the global region order.
+ private Set<String> getRegionsAndAncestors(Set<String> regions) {
+ Set<String> s = new HashSet<>();
+
+ for (String region : regions) {
+ s.add(region);
+
+ if (configuration.getGlobalRegionOrder().contains(region)) {
+ for (String r : configuration.getGlobalRegionOrder()) {
+ if (r.equals(region)) {
+ break;
+ }
+ s.add(r);
+ }
+ } else {
+ Activator.LOG.log(Level.WARNING, "Global API Region order " + configuration.getGlobalRegionOrder() +
+ " does not contain region: " + region);
+ }
+ }
+ return s;
+ }
+
/**
* Check if the package is exported in the global region
* @param packageName The package
@@ -207,7 +229,7 @@
* @return If the feature exports to the global region and the package is exported into the global region
*/
private boolean isInGlobalRegion(String packageName, String capFeat) {
- Set<String> capRegions = this.configuration.getFeatureRegionMap().get(capFeat);
+ List<String> capRegions = this.configuration.getFeatureRegionMap().get(capFeat);
if (capRegions != null && capRegions.contains(RegionConstants.GLOBAL_REGION)) {
Set<String> globalPackages = this.configuration.getRegionPackageMap().get(RegionConstants.GLOBAL_REGION);
if (globalPackages.contains(packageName)) {
@@ -311,7 +333,7 @@
if (packageName == null)
return Collections.emptyList();
- Set<String> regions = this.configuration.getFeatureRegionMap().get(feature);
+ List<String> regions = this.configuration.getFeatureRegionMap().get(feature);
if (regions == null)
return Collections.emptyList();
diff --git a/src/test/java/org/apache/sling/feature/apiregions/impl/RegionConfigurationTest.java b/src/test/java/org/apache/sling/feature/apiregions/impl/RegionConfigurationTest.java
index 714eba9..db3ca70 100644
--- a/src/test/java/org/apache/sling/feature/apiregions/impl/RegionConfigurationTest.java
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/RegionConfigurationTest.java
@@ -27,6 +27,7 @@
import static org.apache.sling.feature.apiregions.impl.RegionConstants.PROPERTIES_RESOURCE_PREFIX;
import static org.apache.sling.feature.apiregions.impl.RegionConstants.REGION_PACKAGE_FILENAME;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -37,6 +38,7 @@
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
@@ -193,9 +195,9 @@
RegionConfiguration re = new RegionConfiguration(ctx);
assertEquals(2, re.featureRegionMap.size());
- assertEquals(Collections.singleton("global"),
+ assertEquals(Collections.singletonList("global"),
re.featureRegionMap.get("an.other:feature:123"));
- assertEquals(new HashSet<>(Arrays.asList("global", "internal")),
+ assertEquals(Arrays.asList("global", "internal"),
re.featureRegionMap.get("org.sling:something:1.2.3"));
assertEquals(f, re.getRegistrationProperties().get(FEATURE_REGION_FILENAME));
}
@@ -217,18 +219,18 @@
re.setConfig("new.config", props);
assertEquals(3, re.featureRegionMap.size());
- assertEquals(Collections.singleton("newregion"),
+ assertEquals(Collections.singletonList("newregion"),
re.featureRegionMap.get("fg1:fa1:3.0"));
- assertEquals(Collections.singleton("global"),
+ assertEquals(Collections.singletonList("global"),
re.featureRegionMap.get("an.other:feature:123"));
- assertEquals(new HashSet<>(Arrays.asList("global", "internal")),
+ assertEquals(Arrays.asList("global", "internal"),
re.featureRegionMap.get("org.sling:something:1.2.3"));
re.removeConfig("new.config");
assertEquals(2, re.featureRegionMap.size());
- assertEquals(Collections.singleton("global"),
+ assertEquals(Collections.singletonList("global"),
re.featureRegionMap.get("an.other:feature:123"));
- assertEquals(new HashSet<>(Arrays.asList("global", "internal")),
+ assertEquals(Arrays.asList("global", "internal"),
re.featureRegionMap.get("org.sling:something:1.2.3"));
}
@@ -493,6 +495,19 @@
expected, caps2);
}
+ @Test
+ public void testRegionOrderProperty() throws Exception {
+ BundleContext ctx = Mockito.mock(BundleContext.class);
+ Mockito.when(ctx.getBundle()).thenReturn(Mockito.mock(Bundle.class));
+ Mockito.when(ctx.getProperty(PROPERTIES_FILE_LOCATION)).
+ thenReturn("classloader://props1");
+
+ RegionConfiguration re = new RegionConfiguration(ctx);
+ assertEquals(Arrays.asList("global", "internal"), re.getGlobalRegionOrder());
+ assertEquals(2, re.getFeatureRegionMap().size());
+ assertNull(re.getFeatureRegionMap().get("__region.order__"));
+ }
+
private BundleRequirement mockRequirement(String bsn, Version bver, BundleContext mockContext) {
BundleRevision br = mockBundleRevision(bsn, bver, mockContext);
@@ -567,10 +582,11 @@
}
}
- private void assertMapUnmodifiable(Map<String, Set<String>> m) {
- Map.Entry<String, Set<String>> entry = m.entrySet().iterator().next();
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private void assertMapUnmodifiable(Map<String, ? extends Collection<String>> m) {
+ Map.Entry<String, ? extends Collection<String>> entry = m.entrySet().iterator().next();
try {
- Set<String> s = entry.getValue();
+ Collection<String> s = entry.getValue();
s.add("testing");
fail("Changing a value should have thrown an exception");
} catch (Exception ex) {
@@ -578,7 +594,8 @@
}
try {
- m.put("foo", Collections.<String>emptySet());
+ Map m2 = m;
+ m2.put("foo", Collections.<String>emptySet());
fail("Adding a new value should have thrown an exception");
} catch (Exception ex) {
// good
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
index bf9d5fb..3f27cb9 100644
--- a/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java
+++ b/src/test/java/org/apache/sling/feature/apiregions/impl/ResolverHookImplTest.java
@@ -54,8 +54,9 @@
bfmap.put("b1", Collections.singleton("f1"));
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f2", Collections.singleton("r2"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f2", Collections.singletonList("r2"));
+ frmap.put("__region.order__", Arrays.asList("global", "r2"));
Map<String, Set<String>> rpmap = new HashMap<>();
@@ -81,8 +82,9 @@
Map<String, Set<String>> bfmap = new HashMap<>();
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f2", Collections.singleton("r2"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f2", Collections.singletonList("r2"));
+ frmap.put("__region.order__", Arrays.asList("global", "r2"));
Map<String, Set<String>> rpmap = new HashMap<>();
@@ -109,9 +111,10 @@
bfmap.put("b1", Collections.singleton("f1"));
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", Collections.emptySet());
- frmap.put("f2", Collections.singleton("r2"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Collections.emptyList());
+ frmap.put("f2", Collections.singletonList("r2"));
+ frmap.put("__region.order__", Arrays.asList("global", "r2"));
Map<String, Set<String>> rpmap = new HashMap<>();
@@ -138,9 +141,10 @@
bfmap.put("b1", Collections.singleton("f1"));
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", Collections.singleton("r2"));
- frmap.put("f2", Collections.singleton("r2"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Collections.singletonList("r2"));
+ frmap.put("f2", Collections.singletonList("r2"));
+ frmap.put("__region.order__", Arrays.asList("global", "r2"));
Map<String, Set<String>> rpmap = new HashMap<>();
@@ -173,10 +177,59 @@
bfmap.put("b11", Collections.singleton("f11"));
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f10", new HashSet<>(Arrays.asList("r2", "r3")));
- frmap.put("f11", new HashSet<>(Arrays.asList("r1", "r2")));
- frmap.put("f2", Collections.singleton("r1"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f10", Arrays.asList("r2", "r3"));
+ frmap.put("f11", Arrays.asList("r1", "r2"));
+ frmap.put("f2", Collections.singletonList("r1"));
+ frmap.put("__region.order__", Arrays.asList("global", "r1", "r2", "r3", "r4"));
+
+ Map<String, Set<String>> rpmap = new HashMap<>();
+ rpmap.put("r1", Collections.singleton("org.foo.bar"));
+ rpmap.put("r2", new HashSet<>(Arrays.asList("xxx", "yyy", "zzz")));
+ rpmap.put("r3", new HashSet<>(Arrays.asList("org.foo.bar", "zzz")));
+
+ ResolverHookImpl rh = new ResolverHookImpl(new RegionConfiguration(bsnvermap, bfmap, frmap, rpmap, Collections.singleton("global")));
+
+ // b2 needs to resolve to 'org.foo.bar' package. b2 is in region r1.
+ // The package is provided by 3 bundles:
+ // b10 provides it but is not in a matching region
+ // b11 provides it and has a matching region
+ // b19 provides it in the global region
+ // Only b11 should provide the capability. Even though b19 provides it from the global region, if there is an overlapping
+ // specific region then the global region should not be used
+ BundleRequirement req1 = mockRequirement("b2", bsnvermap);
+ BundleCapability cap1 = mockCapability("org.foo.bar", "b10", bsnvermap);
+ BundleCapability cap2 = mockCapability("org.foo.bar", "b11", bsnvermap);
+ BundleCapability cap3 = mockCapability("org.foo.bar", "b19", bsnvermap);
+ List<BundleCapability> candidates1 = new ArrayList<>(Arrays.asList(cap1, cap2, cap3));
+ rh.filterMatches(req1, candidates1);
+
+ assertEquals("Only the capability coming from bundle b11 should be selected, b10 is in a different region and b19 is in global "
+ + "which should be excluded as there is a capability in a matching region.",
+ Collections.singletonList(cap2), candidates1);
+ }
+
+ @Test
+ public void testRegionHasPrecedenceOverGlobalRegionOrderNotSet() {
+ Map<Entry<String, Version>, List<String>> bsnvermap = new HashMap<>();
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "providing.bundle.otherfeature", new Version(1,0,0)), Collections.singletonList("b10"));
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "providing.bundle.infeature", new Version(1,0,0)), Collections.singletonList("b11"));
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "providing.bundle.inglobal", new Version(1,0,0)), Collections.singletonList("b19"));
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "requiring.bundle", new Version(1,0,0)), Collections.singletonList("b2"));
+
+ Map<String, Set<String>> bfmap = new HashMap<>();
+ bfmap.put("b10", Collections.singleton("f10"));
+ bfmap.put("b11", Collections.singleton("f11"));
+ bfmap.put("b2", Collections.singleton("f2"));
+
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f10", Arrays.asList("r2", "r3"));
+ frmap.put("f11", Arrays.asList("r1", "r2"));
+ frmap.put("f2", Collections.singletonList("r1"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("r1", Collections.singleton("org.foo.bar"));
@@ -215,8 +268,9 @@
Map<String, Set<String>> bfmap = new HashMap<>();
bfmap.put("b1", Collections.singleton("f1"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", Collections.emptySet());
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Collections.emptyList());
+ frmap.put("__region.order__", Arrays.asList("global", "r1"));
Map<String, Set<String>> rpmap = new HashMap<>();
@@ -247,8 +301,9 @@
bfmap.put("b11", Collections.singleton("f1"));
bfmap.put("b2", Collections.singleton("f1"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", Collections.singleton("r1"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Collections.singletonList("r1"));
+ frmap.put("__region.order__", Arrays.asList("global", "r1"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("r1", Collections.emptySet());
@@ -284,8 +339,9 @@
bfmap.put("b99", Collections.singleton("f1"));
bfmap.put("b101", Collections.singleton("f1"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", new HashSet<>(Arrays.asList("r1", "global")));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Arrays.asList("r1", "global"));
+ frmap.put("__region.order__", Arrays.asList("global", "r1"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("r1", Collections.singleton("org.blah.blah"));
@@ -323,9 +379,10 @@
bfmap.put("b10", Collections.singleton("f10"));
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f10", new HashSet<>(Arrays.asList("r2", "r3")));
- frmap.put("f2", Collections.singleton("r1"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f10", Arrays.asList("r2", "r3"));
+ frmap.put("f2", Collections.singletonList("r1"));
+ frmap.put("__region.order__", Arrays.asList("global", "r1", "r2", "r3"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("r1", Collections.emptySet());
@@ -362,7 +419,7 @@
"requiring.bundle", new Version(1,0,0)), Collections.singletonList("b2"));
Map<String, Set<String>> bfmap = new HashMap<>();
- Map<String, Set<String>> frmap = new HashMap<>();
+ Map<String, List<String>> frmap = new HashMap<>();
Map<String, Set<String>> rpmap = new HashMap<>();
ResolverHookImpl rh = new ResolverHookImpl(new RegionConfiguration(bsnvermap, bfmap, frmap, rpmap, Collections.singleton("global")));
@@ -411,12 +468,13 @@
bfmap.put("b19", Collections.singleton("f3"));
bfmap.put("b20", Collections.singleton("f4"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f", new HashSet<>(Arrays.asList("r1", "r2", RegionConstants.GLOBAL_REGION)));
- frmap.put("f1", Collections.singleton("r1"));
- frmap.put("f2", Collections.singleton("r2"));
- frmap.put("f3", Collections.singleton("r3"));
- frmap.put("f4", Collections.singleton("r3"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f", Arrays.asList("r1", "r2", RegionConstants.GLOBAL_REGION));
+ frmap.put("f1", Collections.singletonList("r1"));
+ frmap.put("f2", Collections.singletonList("r2"));
+ frmap.put("f3", Collections.singletonList("r3"));
+ frmap.put("f4", Collections.singletonList("r3"));
+ frmap.put("__region.order__", Arrays.asList("global", "r1", "r2", "r3"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("r0", Collections.singleton("org.bar"));
@@ -550,11 +608,10 @@
bfmap.put("b1", Collections.singleton("f1"));
bfmap.put("b2", Collections.singleton("f2"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", new HashSet<>(Arrays.asList(
- RegionConstants.GLOBAL_REGION, "org.foo.blah")));
- frmap.put("f2", new HashSet<>(Arrays.asList("org.foo.bar",
- RegionConstants.GLOBAL_REGION, "org.foo.blah")));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Arrays.asList(RegionConstants.GLOBAL_REGION, "org.foo.blah"));
+ frmap.put("f2", Arrays.asList("org.foo.bar", RegionConstants.GLOBAL_REGION, "org.foo.blah"));
+ frmap.put("__region.order__", Arrays.asList("global", "org.foo.blah", "org.foo.bar"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("org.foo.bar", Collections.singleton("org.test"));
@@ -572,8 +629,8 @@
@Test
public void testGetRegionsForPackage() {
- Set<String> regions = new HashSet<>(Arrays.asList("r1", "r2", "r3"));
- Map<String, Set<String>> featureRegionMap = Collections.singletonMap("f2", regions);
+ List<String> regions = Arrays.asList("r1", "r2", "r3");
+ Map<String, List<String>> featureRegionMap = Collections.singletonMap("f2", regions);
Map<String, Set<String>> regionPackageMap = new HashMap<>();
regionPackageMap.put("r2", Collections.singleton("a.b.c"));
@@ -609,16 +666,17 @@
bfmap.put("b98", Collections.singleton("f2"));
bfmap.put("b100", Collections.singleton("f1"));
- Map<String, Set<String>> frmap = new HashMap<>();
- frmap.put("f1", Collections.singleton("r1"));
- frmap.put("f2", Collections.singleton("r2"));
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("f1", Collections.singletonList("r1"));
+ frmap.put("f2", Collections.singletonList("r2"));
+ frmap.put("__region.order__", Arrays.asList("global", "r0", "r1", "r2", "r3"));
Map<String, Set<String>> rpmap = new HashMap<>();
rpmap.put("r1", Collections.singleton("org.test"));
rpmap.put("r2", Collections.singleton("org.test"));
ResolverHookImpl rh = new ResolverHookImpl(new RegionConfiguration(bsnvermap, bfmap, frmap, rpmap,
- new HashSet<>(Arrays.asList("r1", "r3"))));
+ new HashSet<>(Arrays.asList("r0", "r1"))));
// b99 is not in any region itself and tries to resolve to b100 which is in r1
// b99 can resolve to b100 because 'r1' is listed as a default region in the
@@ -703,6 +761,57 @@
assertEquals(expected, rhi4.getFeaturesForBundle(b4));
}
+ @Test
+ public void testRegionOrderInheritance() {
+ Map<Entry<String, Version>, List<String>> bsnvermap = new HashMap<>();
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "b1", new Version(1,0,0)), Collections.singletonList("b1"));
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "b2", new Version(1,0,0)), Collections.singletonList("b2"));
+
+ Map<String, Set<String>> bfmap = new HashMap<>();
+ bfmap.put("b1", Collections.singleton("g:f1:1"));
+ bfmap.put("b2", Collections.singleton("g:f2:1"));
+
+ Map<String, List<String>> frmap = new HashMap<>();
+ frmap.put("g:f1:1", Arrays.asList("deprecated", "internal"));
+ frmap.put("g:f2:1", Collections.singletonList("internal"));
+ frmap.put("__region.order__", Arrays.asList("global", "deprecated", "internal"));
+
+ Map<String, Set<String>> rpmap = new HashMap<>();
+ rpmap.put("internal", Collections.singleton("xyz"));
+ rpmap.put("deprecated", Collections.singleton("abc"));
+
+ ResolverHookImpl rh = new ResolverHookImpl(new RegionConfiguration(bsnvermap, bfmap, frmap, rpmap, Collections.singleton("global")));
+
+ // b2 needs to resolve to the "abc" package, which is exported into the 'deprecated' region.
+ // f2 doesn't directly see the 'deprecated' region (only internal), but since it's declared before
+ // the internal region by f1, f2 implicitly gets visibility of it because it comes before the
+ // internal region in the global region ordering.
+ BundleRequirement req1 = mockRequirement("b2", bsnvermap);
+ BundleCapability cap1 = mockCapability("abc", "b1", bsnvermap);
+ List<BundleCapability> candidates1 = new ArrayList<>(Arrays.asList(cap1));
+ rh.filterMatches(req1, candidates1);
+
+ assertEquals(Collections.singletonList(cap1), candidates1);
+ }
+
+ @Test
+ public void testEmptyCandidates() {
+ Map<Entry<String, Version>, List<String>> bsnvermap = new HashMap<>();
+ bsnvermap.put(new AbstractMap.SimpleEntry<String,Version>(
+ "b1", new Version(1,0,0)), Collections.singletonList("b1"));
+ Map<String, Set<String>> bfmap = new HashMap<>();
+ Map<String, List<String>> frmap = new HashMap<>();
+ Map<String, Set<String>> rpmap = new HashMap<>();
+
+ ResolverHookImpl rh = new ResolverHookImpl(new RegionConfiguration(bsnvermap, bfmap, frmap, rpmap, Collections.singleton("global")));
+ BundleRequirement req1 = mockRequirement("b1", bsnvermap);
+ List<BundleCapability> candidates1 = new ArrayList<>();
+ rh.filterMatches(req1, candidates1);
+ assertEquals("There were no candidates, there still are none", 0, candidates1.size());
+ }
+
private BundleCapability mockCapability(String pkgName, String bid, Map<Entry<String, Version>, List<String>> bsnvermap) {
for (Map.Entry<Map.Entry<String, Version>, List<String>> entry : bsnvermap.entrySet()) {
if (entry.getValue().contains(bid)) {
diff --git a/src/test/resources/features1.properties b/src/test/resources/features1.properties
index 9efad8d..0bb6f89 100644
--- a/src/test/resources/features1.properties
+++ b/src/test/resources/features1.properties
@@ -1,4 +1,5 @@
#Generated at Sat Nov 03 11:10:29 GMT 2018
#Sat Nov 03 11:10:29 GMT 2018
an.other\:feature\:123=global
-org.sling\:something\:1.2.3=internal,global
+org.sling\:something\:1.2.3=global,internal
+__region.order__=global,internal
\ No newline at end of file
diff --git a/src/test/resources/props1/features.properties b/src/test/resources/props1/features.properties
index 9efad8d..0bb6f89 100644
--- a/src/test/resources/props1/features.properties
+++ b/src/test/resources/props1/features.properties
@@ -1,4 +1,5 @@
#Generated at Sat Nov 03 11:10:29 GMT 2018
#Sat Nov 03 11:10:29 GMT 2018
an.other\:feature\:123=global
-org.sling\:something\:1.2.3=internal,global
+org.sling\:something\:1.2.3=global,internal
+__region.order__=global,internal
\ No newline at end of file
diff --git a/src/test/resources/props2/features.properties b/src/test/resources/props2/features.properties
index c2cb887..4597034 100644
--- a/src/test/resources/props2/features.properties
+++ b/src/test/resources/props2/features.properties
@@ -1,3 +1,4 @@
#Generated at Sat Nov 03 11:10:29 GMT 2018
#Sat Nov 03 11:10:29 GMT 2018
org.sling\:something\:1.2.3=r0,r1,r2,r3
+__region.order__=global,r0,r1,r2,r3
\ No newline at end of file
diff --git a/src/test/resources/props3/features.properties b/src/test/resources/props3/features.properties
index 9efad8d..0bb6f89 100644
--- a/src/test/resources/props3/features.properties
+++ b/src/test/resources/props3/features.properties
@@ -1,4 +1,5 @@
#Generated at Sat Nov 03 11:10:29 GMT 2018
#Sat Nov 03 11:10:29 GMT 2018
an.other\:feature\:123=global
-org.sling\:something\:1.2.3=internal,global
+org.sling\:something\:1.2.3=global,internal
+__region.order__=global,internal
\ No newline at end of file
diff --git a/src/test/resources/props4/features.properties b/src/test/resources/props4/features.properties
index 9a47d1a..8bb06a1 100644
--- a/src/test/resources/props4/features.properties
+++ b/src/test/resources/props4/features.properties
@@ -1,3 +1,4 @@
#Generated at Sat Nov 03 11:10:29 GMT 2018
#Sat Nov 03 11:10:29 GMT 2018
-org.sling\:something\:1.2.3=internal,global
+org.sling\:something\:1.2.3=global,internal
+__region.order__=global,internal
\ No newline at end of file