You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2020/01/16 15:10:45 UTC

[sling-org-apache-sling-feature-extension-apiregions] branch issues/SLING-9007 created (now 904eb3c)

This is an automated email from the ASF dual-hosted git repository.

pauls pushed a change to branch issues/SLING-9007
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git.


      at 904eb3c  SLING-9007: Base the region calculation on the feature-origin and add a feature-origin to apiregions during merge

This branch includes the following new commits:

     new 904eb3c  SLING-9007: Base the region calculation on the feature-origin and add a feature-origin to apiregions during merge

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-feature-extension-apiregions] 01/01: SLING-9007: Base the region calculation on the feature-origin and add a feature-origin to apiregions during merge

Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

pauls pushed a commit to branch issues/SLING-9007
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git

commit 904eb3ce341acc20a4aee6ce2e0c80970ff40ad3
Author: Karl Pauls <kp...@adobe.com>
AuthorDate: Thu Jan 16 16:10:14 2020 +0100

    SLING-9007: Base the region calculation on the feature-origin and add a feature-origin to apiregions during merge
---
 pom.xml                                            |  12 +-
 .../apiregions/APIRegionMergeHandler.java          |  70 ++-------
 .../extension/apiregions/AbstractHandler.java      |  95 ------------
 .../apiregions/BundleArtifactFeatureHandler.java   | 123 ----------------
 .../extension/apiregions/BundleMappingHandler.java |  65 ---------
 .../CheckApiRegionsBundleExportsImports.java       |  82 ++++-------
 .../extension/apiregions/api/ApiRegion.java        |  41 ++++++
 .../extension/apiregions/api/ApiRegions.java       |   8 ++
 .../apiregions/APIRegionMergeHandlerTest.java      |  98 +------------
 .../BundleArtifactFeatureHandlerTest.java          | 152 --------------------
 .../apiregions/BundleMappingHandlerTest.java       | 160 ---------------------
 .../CheckApiRegionsBundleExportsImportsTest.java   |  76 +++++-----
 .../f_f_1/bundleOrigins.properties                 |   2 -
 .../f_f_1/regionOrigins.properties                 |   2 -
 .../f_f_1/bundleOrigins.properties                 |   2 -
 .../f_f_1/regionOrigins.properties                 |   2 -
 .../f_f_1/bundleOrigins.properties                 |   2 -
 .../f_f_1/regionOrigins.properties                 |   2 -
 .../f_f_1/bundleOrigins.properties                 |   2 -
 .../f_f_1/regionOrigins.properties                 |   1 -
 .../f_f_1/bundleOrigins.properties                 |   2 -
 .../f_f_1/regionOrigins.properties                 |   2 -
 .../f_f_2/bundleOrigins.properties                 |   4 -
 .../f_f_2/regionOrigins.properties                 |   3 -
 24 files changed, 146 insertions(+), 862 deletions(-)

diff --git a/pom.xml b/pom.xml
index c051ab4..9debc0b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,13 +62,13 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.feature</artifactId>
-            <version>1.1.0</version>
+            <version>1.1.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.feature.analyser</artifactId>
-            <version>1.1.0</version>
+            <version>1.2.3-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -99,8 +99,14 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.feature.io</artifactId>
-            <version>1.0.4</version>
+            <version>1.2.1-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <version>1.11.2</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java b/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java
index feb37b9..45cc3f5 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java
@@ -21,6 +21,7 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Properties;
 import java.util.stream.Collectors;
@@ -28,6 +29,7 @@ import java.util.stream.Collectors;
 import javax.json.JsonArray;
 
 import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.builder.HandlerContext;
@@ -53,13 +55,9 @@ public class APIRegionMergeHandler implements MergeHandler {
         if (targetEx != null && !targetEx.getName().equals(ApiRegions.EXTENSION_NAME))
             return;
 
-        storeBundleOrigins(context, source, target);
-
         try {
             final ApiRegions srcRegions = ApiRegions.parse((JsonArray) sourceEx.getJSONStructure());
 
-            storeRegionOrigins(context, source, target, srcRegions);
-
             final ApiRegions targetRegions;
             if (targetEx != null) {
                 targetRegions = ApiRegions.parse((JsonArray) targetEx.getJSONStructure());
@@ -79,11 +77,17 @@ public class APIRegionMergeHandler implements MergeHandler {
                             targetRegion.add(srcExp);
                         }
                     }
+                    LinkedHashSet<ArtifactId> origins = new LinkedHashSet<>(Arrays.asList(targetRegion.getFeatureOrigins()));
+                    origins.add(source.getId());
+                    targetRegion.setFeatureOrigins(origins.toArray(new ArtifactId[0]));
                 }
             }
 
             // If there are any remaining regions in the src extension, process them now
             for (final ApiRegion r : srcRegions.listRegions()) {
+                LinkedHashSet<ArtifactId> origins = new LinkedHashSet<>(Arrays.asList(r.getFeatureOrigins()));
+                origins.add(source.getId());
+                r.setFeatureOrigins(origins.toArray(new ArtifactId[0]));
                 if (!targetRegions.add(r)) {
                     throw new IllegalStateException("Duplicate region " + r.getName());
                 }
@@ -95,62 +99,4 @@ public class APIRegionMergeHandler implements MergeHandler {
             throw new RuntimeException(e);
         }
     }
-
-    private void storeRegionOrigins(HandlerContext context, Feature source, Feature target, ApiRegions regions) {
-        try {
-            File f = AbstractHandler.getFeatureDataFile(context, target, "regionOrigins.properties");
-
-            Properties p = new Properties();
-            if (f.isFile()) {
-                try (FileInputStream fis = new FileInputStream(f)) {
-                    p.load(fis);
-                }
-            }
-
-            String fid = source.getId().toMvnId();
-            p.put(fid, regions.listRegions().stream().map(region -> region.getName()).collect(Collectors.joining(",")));
-
-            try (FileOutputStream fos = new FileOutputStream(f)) {
-                p.store(fos, "Mapping from feature ID to regions that the feature is a member of");
-            }
-        } catch (IOException e) {
-            throw new IllegalStateException("Problem storing region origin information", e);
-        }
-    }
-
-    private void storeBundleOrigins(HandlerContext context, Feature source, Feature target) {
-        try {
-            File f = AbstractHandler.getFeatureDataFile(context, target, "bundleOrigins.properties");
-
-            String featureId = source.getId().toMvnId();
-            Properties p = new Properties();
-            if (f.isFile()) {
-                try (FileInputStream fis = new FileInputStream(f)) {
-                    p.load(fis);
-                }
-            }
-
-            for (Artifact b : source.getBundles()) {
-                String bundleId = b.getId().toMvnId();
-                String org = p.getProperty(bundleId);
-                String newVal;
-                if (org != null) {
-                    List<String> l = Arrays.asList(org.split(","));
-                    if (!l.contains(featureId))
-                        newVal = org + "," + featureId;
-                    else
-                        newVal = org;
-                } else {
-                    newVal = featureId;
-                }
-                p.setProperty(bundleId, newVal);
-            }
-
-            try (FileOutputStream fos = new FileOutputStream(f)) {
-                p.store(fos, "Mapping from bundle artifact IDs to features that contained the bundle.");
-            }
-        } catch (IOException e) {
-            throw new IllegalStateException("Problem storing bundle origin information", e);
-        }
-    }
 }
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/AbstractHandler.java b/src/main/java/org/apache/sling/feature/extension/apiregions/AbstractHandler.java
deleted file mode 100644
index 78ddb25..0000000
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/AbstractHandler.java
+++ /dev/null
@@ -1,95 +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.sling.feature.extension.apiregions;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Date;
-import java.util.Properties;
-
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.HandlerContext;
-
-class AbstractHandler {
-    static final String GLOBAL_NAME = "global";
-
-    static final String NAME_KEY = "name";
-    static final String EXPORTS_KEY = "exports";
-
-    static final String FILE_PREFIX = "apiregions.";
-    static final String FILE_STORAGE_DIR_KEY = "fileStorage";
-
-    static final String PROPERTIES_RESOURCE_PREFIX = "sling.feature.apiregions.resource.";
-
-    protected static File getFeatureDataFile(HandlerContext context, Feature target, String fileName) throws IOException {
-        String featureName = target.getId().toMvnId().replaceAll("[^a-zA-Z0-9\\.\\-]", "_");
-        File f = AbstractHandler.getDataFile(context, featureName, fileName);
-        f.getParentFile().mkdirs();
-        return f;
-    }
-
-    protected static File getDataFile(HandlerContext context, String directory, String name) throws IOException {
-        String stg = context.getConfiguration().get(FILE_STORAGE_DIR_KEY);
-        File f;
-        if (stg != null) {
-            File dir;
-            if (directory != null) {
-                dir = new File(stg, directory);
-                dir.mkdirs();
-            } else {
-                dir = new File(stg);
-            }
-            f = new File(dir, name);
-        } else {
-            // If we store in the temp space we don't use the directory
-            Path p = Files.createTempFile(FILE_PREFIX, name);
-            f = p.toFile();
-            f.deleteOnExit();
-        }
-
-        // The feature launcher runtime picks the data file up from this system property
-        System.setProperty(PROPERTIES_RESOURCE_PREFIX + name, f.getCanonicalPath());
-        return f;
-    }
-
-    protected static File getDataFile(HandlerContext context, String name) throws IOException {
-        return getDataFile(context, null, name);
-    }
-
-    protected Properties loadProperties(File file) throws IOException, FileNotFoundException {
-        Properties map = new Properties();
-        if (file.exists()) {
-            try (InputStream is = new FileInputStream(file)) {
-                map.load(is);
-            }
-        }
-        return map;
-    }
-
-    protected void storeProperties(Properties properties, File file) throws IOException, FileNotFoundException {
-        try (OutputStream os = new FileOutputStream(file)) {
-            properties.store(os, "Generated at " + new Date());
-        }
-    }
-}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/BundleArtifactFeatureHandler.java b/src/main/java/org/apache/sling/feature/extension/apiregions/BundleArtifactFeatureHandler.java
deleted file mode 100644
index a3df8ff..0000000
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/BundleArtifactFeatureHandler.java
+++ /dev/null
@@ -1,123 +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.sling.feature.extension.apiregions;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonValue;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.HandlerContext;
-import org.apache.sling.feature.builder.PostProcessHandler;
-import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
-
-public class BundleArtifactFeatureHandler extends AbstractHandler implements PostProcessHandler {
-    @Override
-    public void postProcess(HandlerContext context, Feature feature, Extension extension) {
-        if (!ApiRegions.EXTENSION_NAME.equals(extension.getName()))
-            return;
-
-        try {
-            writeBundleToFeatureMap(context, feature);
-            writeFeatureToRegionAndPackageMap(context, feature, extension);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void writeBundleToFeatureMap(HandlerContext context, Feature feature) throws IOException {
-        File bundlesFile = getFeatureDataFile(context, feature, "bundles.properties");
-        Properties map = loadProperties(bundlesFile);
-
-        for (Artifact b : feature.getBundles()) {
-            String id = b.getId().toMvnId().trim();
-            String fid = feature.getId().toMvnId().trim();
-
-            String m = map.getProperty(id);
-            if (m != null) {
-                List<String> l = Arrays.asList(m.split(","));
-                if (!l.contains(fid))
-                    m = m.trim() + "," + fid;
-            } else {
-                m = fid;
-            }
-            map.put(id, m);
-        }
-
-        storeProperties(map, bundlesFile);
-    }
-
-    private void writeFeatureToRegionAndPackageMap(HandlerContext context, Feature feature, Extension extension) throws IOException {
-        try (JsonReader jr = Json.createReader(new StringReader(extension.getJSON()))) {
-            JsonArray ja = jr.readArray();
-
-            File featuresFile = getFeatureDataFile(context, feature, "features.properties");
-            File regionsFile = getFeatureDataFile(context, feature, "regions.properties");
-            Properties frMap = loadProperties(featuresFile);
-            Properties rpMap = loadProperties(regionsFile);
-
-            for (JsonValue jv : ja) {
-                if (jv instanceof JsonObject) {
-                    JsonObject jo = (JsonObject) jv;
-                    String fid = feature.getId().toMvnId();
-
-                    // Regions are ordered, so need to use a linked hash set
-                    Set<String> regionSet = new LinkedHashSet<>();
-                    String regions = frMap.getProperty(fid);
-                    if (regions != null) {
-                        regionSet.addAll(Arrays.asList(regions.split(",")));
-                    }
-                    String region = jo.getString(NAME_KEY);
-                    regionSet.add(region);
-
-                    frMap.put(fid, regionSet.stream().collect(Collectors.joining(",")));
-
-                    Set<String> packageSet = new HashSet<>();
-                    String packages = rpMap.getProperty(region);
-                    if (packages != null) {
-                        packageSet.addAll(Arrays.asList(packages.split(",")));
-                    }
-                    if (jo.containsKey(EXPORTS_KEY)) {
-                        JsonArray eja = jo.getJsonArray(EXPORTS_KEY);
-                        for (int i=0; i < eja.size(); i++) {
-                            packageSet.add(eja.getString(i));
-                        }
-                    }
-                    rpMap.put(region, packageSet.stream().collect(Collectors.joining(",")));
-                }
-            }
-
-            storeProperties(frMap, featuresFile);
-            storeProperties(rpMap, regionsFile);
-        }
-    }
-}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/BundleMappingHandler.java b/src/main/java/org/apache/sling/feature/extension/apiregions/BundleMappingHandler.java
deleted file mode 100644
index 88ca215..0000000
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/BundleMappingHandler.java
+++ /dev/null
@@ -1,65 +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.sling.feature.extension.apiregions;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Properties;
-import java.util.jar.JarFile;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.HandlerContext;
-import org.apache.sling.feature.builder.PostProcessHandler;
-import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
-import org.apache.sling.feature.io.IOUtils;
-import org.osgi.framework.Constants;
-
-public class BundleMappingHandler extends AbstractHandler implements PostProcessHandler {
-    @Override
-    public void postProcess(HandlerContext context, Feature feature, Extension extension) {
-        if (!ApiRegions.EXTENSION_NAME.equals(extension.getName()))
-            return;
-
-        try {
-            File idBSNFile = getFeatureDataFile(context, feature, "idbsnver.properties");
-            Properties map = loadProperties(idBSNFile);
-
-            for (Artifact b : feature.getBundles()) {
-                URL f = context.getArtifactProvider().provide(b.getId());
-
-                try (JarFile jf = IOUtils.getJarFileFromURL(f, true, null)) {
-                    String bsn = jf.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-                    if (bsn == null)
-                        continue;
-
-                    String ver = jf.getManifest().getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-                    if (ver == null)
-                        ver = "0.0.0";
-
-                    map.put(b.getId().toMvnId(), bsn.trim() + "~" + ver.trim());
-                }
-            }
-
-            storeProperties(map, idBSNFile);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
index 2b1a884..d83000e 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java
@@ -26,6 +26,7 @@ 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;
@@ -36,16 +37,20 @@ import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.json.JsonArray;
 
+import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Extension;
 import org.apache.sling.feature.Extensions;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.analyser.task.AnalyserTask;
 import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.extension.apiregions.api.ApiRegion;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
 import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.FeatureDescriptor;
 import org.apache.sling.feature.scanner.PackageInfo;
 import org.osgi.framework.Version;
 
@@ -146,31 +151,21 @@ public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
         if ( ctx.getFrameworkDescriptor() != null ) {
             exportingBundles.add(ctx.getFrameworkDescriptor());
         }
-
-        Map<String, Set<String>> bundleToOriginalFeatures;
-        Map<String, Set<String>> featureToOriginalRegions;
         ApiRegions apiRegions = new ApiRegions(); // Empty API Regions;
 
-        if (ignoreAPIRegions) {
-            bundleToOriginalFeatures = Collections.emptyMap();
-            featureToOriginalRegions = Collections.emptyMap();
-        } else {
-            bundleToOriginalFeatures = readBundleOrigins(ctx);
-            featureToOriginalRegions = readRegionOrigins(ctx);
-            Feature feature = ctx.getFeature();
-
-            // extract and check the api-regions
-
-            Extensions extensions = feature.getExtensions();
-            Extension apiRegionsExtension = extensions.getByName(ApiRegions.EXTENSION_NAME);
-            if (apiRegionsExtension != null && apiRegionsExtension.getJSONStructure() != null) {
-                try {
-                    apiRegions = ApiRegions.parse((JsonArray) apiRegionsExtension.getJSONStructure());
-                } catch (IOException e) {
-                    ctx.reportError("API Regions '" + apiRegionsExtension.getJSON()
-                            + "' does not represent a valid JSON 'api-regions': " + e.getMessage());
-                    return;
-                }
+        Feature feature = ctx.getFeature();
+
+        // extract and check the api-regions
+
+        Extensions extensions = feature.getExtensions();
+        Extension apiRegionsExtension = extensions.getByName(ApiRegions.EXTENSION_NAME);
+        if (apiRegionsExtension != null && apiRegionsExtension.getJSONStructure() != null) {
+            try {
+                apiRegions = ApiRegions.parse((JsonArray) apiRegionsExtension.getJSONStructure());
+            } catch (IOException e) {
+                ctx.reportError("API Regions '" + apiRegionsExtension.getJSON()
+                        + "' does not represent a valid JSON 'api-regions': " + e.getMessage());
+                return;
             }
         }
 
@@ -186,8 +181,7 @@ public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
                 if ( info.getImportedPackages() != null ) {
                     for(final PackageInfo pck : info.getImportedPackages() ) {
                         final Map<BundleDescriptor, Set<String>> candidates =
-                                getCandidates(exportingBundles, pck, info,
-                                        bundleToOriginalFeatures, featureToOriginalRegions, apiRegions);
+                                getCandidates(exportingBundles, pck, info, apiRegions, ignoreAPIRegions);
                         if ( candidates.isEmpty() ) {
                             if ( pck.isOptional() ) {
                                 getReport(reports, info).missingExportsForOptional.add(pck);
@@ -221,7 +215,7 @@ public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
 
                                     // Find out what regions the importing bundle is in
                                     Set<String> imRegions =
-                                            getBundleRegions(info, bundleToOriginalFeatures, featureToOriginalRegions);
+                                            getBundleRegions(info, apiRegions, ignoreAPIRegions);
 
                                     // Record the exporting and importing regions for diagnostics
                                     exportingRegions.addAll(exRegions);
@@ -291,21 +285,12 @@ public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
         }
     }
 
-    private Set<String> getBundleRegions(BundleDescriptor info, Map<String, Set<String>> bundleToOriginalFeatures,
-            Map<String, Set<String>> featureToOriginalRegions) {
-        Set<String> result = new HashSet<>();
+    private Set<String> getBundleRegions(BundleDescriptor info, ApiRegions regions, boolean ignoreAPIRegions) {
+        Set<String> result = ignoreAPIRegions ? Collections.emptySet() : Stream.of(info.getArtifact().getFeatureOrigins())
+            .map(regions::getRegionsByFeature).flatMap(Stream::of).map(ApiRegion::getName).collect(Collectors.toSet());
 
-        Set<String> originFeatures = bundleToOriginalFeatures.get(info.getArtifact().getId().toMvnId());
-        if (originFeatures != null) {
-            for (String feature : originFeatures) {
-                Set<String> originRegions = featureToOriginalRegions.get(feature);
-                if (originRegions != null) {
-                    result.addAll(originRegions);
-                }
-            }
-        }
-
-        if (result.size() == 0) {
+        if (result.isEmpty()) {
+            result = new HashSet<>();
             result.add(NO_REGION);
         }
         return result;
@@ -343,22 +328,15 @@ public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
             final List<BundleDescriptor> exportingBundles,
             final PackageInfo pck,
             final BundleDescriptor requestingBundle,
-            final Map<String, Set<String>> bundleToOriginalFeatures,
-            final Map<String, Set<String>> featureToOriginalRegions,
-            final ApiRegions apiRegions) throws IOException {
-        Set<String> rf = bundleToOriginalFeatures.get(
-                requestingBundle.getArtifact().getId().toMvnId());
-        if (rf == null)
-            rf = Collections.emptySet();
+            final ApiRegions apiRegions, boolean ignoreAPIRegions) throws IOException {
+        Set<String> rf = ignoreAPIRegions ? Collections.emptySet() : Stream.of(requestingBundle.getArtifact().getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.toSet());
+
         final Set<String> requestingFeatures = rf;
 
         final Map<BundleDescriptor, Set<String>> candidates = new HashMap<>();
         for(final BundleDescriptor info : exportingBundles) {
             if ( info.isExportingPackage(pck.getName()) ) {
-                Set<String> providingFeatures = bundleToOriginalFeatures.get(
-                        info.getArtifact().getId().toMvnId());
-                if (providingFeatures == null)
-                    providingFeatures = Collections.emptySet();
+                Set<String> providingFeatures = ignoreAPIRegions ? Collections.emptySet() : Stream.of(info.getArtifact().getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.toSet());
 
                 // Compute the intersection without modifying the sets
                 Set<String> intersection = providingFeatures.stream().filter(
@@ -369,7 +347,7 @@ public class CheckApiRegionsBundleExportsImports implements AnalyserTask {
                     continue;
                 }
 
-                for (String region : getBundleRegions(info, bundleToOriginalFeatures, featureToOriginalRegions)) {
+                for (String region : getBundleRegions(info, apiRegions, ignoreAPIRegions)) {
                     if (!NO_REGION.equals(region) &&
                             (apiRegions.getRegionByName(region) == null
                                     || apiRegions.getRegionByName(region).getExportByName(pck.getName()) == null))
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java
index c602a5e..84d68bc 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java
@@ -20,8 +20,16 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
 
 /**
  * Describes an api region
@@ -59,6 +67,39 @@ public class ApiRegion {
         return name;
     }
 
+    public ArtifactId[] getFeatureOrigins() {
+        String origins = this.getProperties().get(Artifact.KEY_FEATURE_ORIGINS);
+        Set<ArtifactId> originFeatures;
+        if (origins == null || origins.trim().isEmpty()) {
+            originFeatures = Collections.emptySet();
+        }
+        else {
+            originFeatures = new LinkedHashSet<>();
+            for (String origin : origins.split(",")) {
+                if (!origin.trim().isEmpty()) {
+                    originFeatures.add(ArtifactId.parse(origin));
+                }
+            }
+        }
+        return originFeatures.toArray(new ArtifactId[0]);
+    }
+
+    public void setFeatureOrigins(ArtifactId... featureOrigins) {
+        String origins;
+        if (featureOrigins != null && featureOrigins.length > 0) {
+            origins = Stream.of(featureOrigins).filter(Objects::nonNull).map(ArtifactId::toMvnId).distinct().collect(Collectors.joining(","));
+        }
+        else {
+            origins = "";
+        }
+        if (!origins.trim().isEmpty()) {
+            this.getProperties().put(Artifact.KEY_FEATURE_ORIGINS, origins);
+        }
+        else {
+            this.getProperties().remove(Artifact.KEY_FEATURE_ORIGINS);
+        }
+    }
+
     /**
      * Add the export. The export is only added if there isn't already a export with
      * the same name
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java
index 17b86b4..2ffaa4a 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java
@@ -21,8 +21,10 @@ import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import javax.json.Json;
 import javax.json.JsonArray;
@@ -155,6 +157,12 @@ public class ApiRegions {
         return found;
     }
 
+    public ApiRegion[] getRegionsByFeature(final ArtifactId featureId) {
+        return this.regions.stream().filter(
+            region -> Stream.of(region.getFeatureOrigins()).anyMatch(featureId::equals)
+        ).toArray(ApiRegion[]::new);
+    }
+
     /**
      * Get the names of the regions
      *
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java
index 04e967b..2d925c8 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java
@@ -104,6 +104,7 @@ public class APIRegionMergeHandlerTest {
         global.add(new ApiExport("a.b.c"));
         global.add(new ApiExport("d.e.f"));
         global.add(new ApiExport("test"));
+        global.setFeatureOrigins(sf.getId());
         expected.add(global);
 
         ApiRegion internal = new ApiRegion("internal");
@@ -114,6 +115,7 @@ public class APIRegionMergeHandlerTest {
         ApiRegion something = new ApiRegion("something");
         something.add(new ApiExport("a.ha"));
         something.getProperties().put("my-key", "my-val");
+        something.setFeatureOrigins(sf.getId());
         expected.add(something);
 
         ApiRegions created = ApiRegions.parse((JsonArray) tgEx.getJSONStructure());
@@ -144,10 +146,10 @@ public class APIRegionMergeHandlerTest {
 
         Extension tgEx = tf.getExtensions().iterator().next();
 
-        String expectedJSON = "[{\"name\":\"global\",\"exports\":[\"a.b.c\",\"d.e.f\"]},"
-                + "{\"name\":\"deprecated\",\"exports\":[\"klm\",\"qrs\"]},"
-                + "{\"name\":\"internal\",\"exports\":[\"xyz\"]},"
-                + "{\"name\":\"forbidden\",\"exports\":[\"abc\",\"klm\"]}]";
+        String expectedJSON = "[{\"name\":\"global\",\"exports\":[\"a.b.c\",\"d.e.f\"],\"feature-origins\":\"y:s:2\"},"
+                + "{\"name\":\"deprecated\",\"exports\":[\"klm\",\"qrs\"],\"feature-origins\":\"y:s:2\"},"
+                + "{\"name\":\"internal\",\"exports\":[\"xyz\"],\"feature-origins\":\"y:s:2\"},"
+                + "{\"name\":\"forbidden\",\"exports\":[\"abc\",\"klm\"],\"feature-origins\":\"y:s:2\"}]";
         JsonReader er = Json.createReader(new StringReader(expectedJSON));
         JsonReader ar = Json.createReader(new StringReader(tgEx.getJSON()));
         JsonArray ea = er.readArray();
@@ -155,92 +157,4 @@ public class APIRegionMergeHandlerTest {
 
         assertEquals(ea, aa);
     }
-
-    @Test
-    public void testStoreBundleOrigins() throws Exception {
-        HandlerContext hc = Mockito.mock(HandlerContext.class);
-        Mockito.when(hc.getConfiguration()).thenReturn(
-                Collections.singletonMap(AbstractHandler.FILE_STORAGE_DIR_KEY,
-                        tempDir.toString()));
-
-        APIRegionMergeHandler armh = new APIRegionMergeHandler();
-
-        Feature tf = new Feature(ArtifactId.fromMvnId("g:t:1"));
-        Feature sf1 = new Feature(ArtifactId.fromMvnId("g:s1:1"));
-        Extension sf1Ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL);
-        sf1Ex.setJSON("[]");
-
-        sf1.getBundles().add(new Artifact(ArtifactId.fromMvnId("a:b1:1")));
-        sf1.getBundles().add(new Artifact(ArtifactId.fromMvnId("a:b2:1")));
-
-        armh.merge(hc, tf, sf1, null, sf1Ex);
-
-        Feature sf2 = new Feature(ArtifactId.fromMvnId("g:s2:1"));
-        Extension sf2Ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL);
-        sf2Ex.setJSON("[]");
-
-        sf2.getBundles().add(new Artifact(ArtifactId.fromMvnId("a:b2:1")));
-        sf2.getBundles().add(new Artifact(ArtifactId.fromMvnId("a:b3:1")));
-        sf2.getBundles().add(new Artifact(ArtifactId.fromMvnId("a:b2:1")));
-
-        armh.merge(hc, tf, sf2, tf.getExtensions().getByName("api-regions"), sf2Ex);
-
-        Feature sf3 = new Feature(ArtifactId.fromMvnId("g:s3:1"));
-        Extension sf3Ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL);
-        sf3Ex.setJSON("[]");
-
-        sf3.getBundles().add(new Artifact(ArtifactId.fromMvnId("a:b2:1")));
-
-        armh.merge(hc, tf, sf3, tf.getExtensions().getByName("api-regions"), sf3Ex);
-
-        Properties bo = new Properties();
-        bo.load(new FileInputStream(new File(tempDir.toFile(), "g_t_1/bundleOrigins.properties")));
-        assertEquals(3, bo.size());
-
-        assertEquals("g:s1:1", bo.get("a:b1:1"));
-        assertEquals("g:s1:1,g:s2:1,g:s3:1", bo.get("a:b2:1"));
-        assertEquals("g:s2:1", bo.get("a:b3:1"));
-    }
-
-    @Test
-    public void testStoreRegionOrigins() throws Exception {
-        HandlerContext hc = Mockito.mock(HandlerContext.class);
-        Mockito.when(hc.getConfiguration()).thenReturn(
-                Collections.singletonMap(AbstractHandler.FILE_STORAGE_DIR_KEY,
-                        tempDir.toString()));
-
-        APIRegionMergeHandler armh = new APIRegionMergeHandler();
-
-        Feature tf = new Feature(ArtifactId.fromMvnId("x:t:1"));
-        Feature sf1 = new Feature(ArtifactId.fromMvnId("y:s:2"));
-
-        Extension sr1Ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL);
-        sr1Ex.setJSON("[{\"name\":\"global\","
-                + "\"exports\": [\"a.b.c\",\"d.e.f\"]},"
-                + "{\"name\":\"deprecated\","
-                + "\"exports\":[\"klm\",\"#ignored\",\"qrs\"]},"
-                + "{\"name\":\"internal\","
-                + "\"exports\":[\"xyz\"]},"
-                + "{\"name\":\"forbidden\","
-                + "\"exports\":[\"abc\",\"klm\"]}]");
-
-        armh.merge(hc, tf, sf1, null, sr1Ex);
-
-        Feature sf2 = new Feature(ArtifactId.fromMvnId("z:s:1"));
-
-        Extension sr2Ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL);
-        sr2Ex.setJSON("[{\"name\":\"global\","
-                + "\"exports\": [\"g.h.i\"]},"
-                + "{\"name\":\"internal\","
-                + "\"exports\":[]},"
-                + "{\"name\":\"somethingelse\","
-                + "\"exports\":[\"qqq\"]}]");
-        armh.merge(hc, tf, sf2, tf.getExtensions().getByName("api-regions"), sr2Ex);
-
-        Properties ro = new Properties();
-        ro.load(new FileInputStream(new File(tempDir.toFile(), "x_t_1/regionOrigins.properties")));
-        assertEquals(2, ro.size());
-        assertEquals("global,deprecated,internal,forbidden", ro.get("y:s:2"));
-        assertEquals("global,internal,somethingelse", ro.get("z:s:1"));
-    }
 }
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/BundleArtifactFeatureHandlerTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/BundleArtifactFeatureHandlerTest.java
deleted file mode 100644
index a4f465e..0000000
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/BundleArtifactFeatureHandlerTest.java
+++ /dev/null
@@ -1,152 +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.sling.feature.extension.apiregions;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.ArtifactProvider;
-import org.apache.sling.feature.builder.HandlerContext;
-import org.junit.Test;
-
-import java.io.FileReader;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-
-import static org.junit.Assert.assertEquals;
-
-public class BundleArtifactFeatureHandlerTest {
-    @Test
-    public void testBundleToFeatureMap() throws Exception {
-        BundleArtifactFeatureHandler bafh = new BundleArtifactFeatureHandler();
-
-        Feature f = new Feature(ArtifactId.fromMvnId("org.sling:something:1.2.3:slingosgifeature:myclassifier"));
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("org.sling:b1:1"));
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("org.sling:b2:1"));
-        Artifact b3a = new Artifact(ArtifactId.fromMvnId("org.sling:b3:1"));
-        Artifact b3b = new Artifact(ArtifactId.fromMvnId("org.sling:b3:1"));
-        f.getBundles().addAll(Arrays.asList(b1, b2, b3a, b3b));
-
-        Extension ex = new Extension(ExtensionType.JSON, "api-regions", false);
-        ex.setJSON("[]");
-        bafh.postProcess(new TestHandlerContextImpl(), f, ex);
-
-        String p = System.getProperty("sling.feature.apiregions.resource.bundles.properties");
-        Properties actual = new Properties();
-        actual.load(new FileReader(p));
-
-        Properties expected = new Properties();
-        expected.put("org.sling:b1:1", "org.sling:something:1.2.3:slingosgifeature:myclassifier");
-        expected.put("org.sling:b2:1", "org.sling:something:1.2.3:slingosgifeature:myclassifier");
-        expected.put("org.sling:b3:1", "org.sling:something:1.2.3:slingosgifeature:myclassifier");
-        assertEquals(expected, actual);
-    }
-
-    @Test
-    public void testFeatureToRegionMap() throws Exception {
-        BundleArtifactFeatureHandler bafh = new BundleArtifactFeatureHandler();
-
-        Feature f = new Feature(ArtifactId.fromMvnId("org.sling:something:1.2.3"));
-        Extension ex = new Extension(ExtensionType.JSON, "api-regions", false);
-        ex.setJSON("[{\"name\":\"global\","
-                + "\"exports\": [\"a.b.c\",\"d.e.f\"]},"
-                + "{\"name\":\"internal\","
-                + "\"exports\":[\"xyz\"]},"
-                + "{\"name\":\"global\","
-                + "\"exports\":[\"test\"]}]");
-
-        bafh.postProcess(new TestHandlerContextImpl(), f, ex);
-
-        String p = System.getProperty("sling.feature.apiregions.resource.features.properties");
-        Properties actual = new Properties();
-        actual.load(new FileReader(p));
-
-        Properties expected = new Properties();
-        expected.put("org.sling:something:1.2.3", "internal,global");
-
-        String[] al = ((String) actual.remove("org.sling:something:1.2.3")).split(",");
-        String[] el = ((String) expected.remove("org.sling:something:1.2.3")).split(",");
-        assertEquals(new HashSet<>(Arrays.asList(el)), new HashSet<>(Arrays.asList(al)));
-        assertEquals(expected, actual);
-
-        String p2 = System.getProperty("sling.feature.apiregions.resource.regions.properties");
-        Properties actual2 = new Properties();
-        actual2.load(new FileReader(p2));
-
-        Properties expected2 = new Properties();
-        expected2.put("internal", "xyz");
-        expected2.put("global", "test,a.b.c,d.e.f");
-
-        String[] agl2 = ((String) actual2.remove("global")).split(",");
-        String[] egl2 = ((String) expected2.remove("global")).split(",");
-        assertEquals(new HashSet<>(Arrays.asList(egl2)), new HashSet<>(Arrays.asList(agl2)));
-        assertEquals(expected2, actual2);
-    }
-
-    @Test
-    public void testRegionOrdering() throws Exception {
-        BundleArtifactFeatureHandler bafh = new BundleArtifactFeatureHandler();
-
-        Feature f = new Feature(ArtifactId.fromMvnId("org.sling:somethingelse:1.0.0"));
-        Extension ex = new Extension(ExtensionType.JSON, "api-regions", false);
-        ex.setJSON("[{\"name\":\"global\","
-                + "\"exports\": []},"
-                + "{\"name\":\"internal\","
-                + "\"exports\":[]},"
-                + "{\"name\":\"private\","
-                + "\"exports\":[]},"
-                + "{\"name\":\"forbidden\","
-                + "\"exports\":[]}]");
-
-        bafh.postProcess(new TestHandlerContextImpl(), f, ex);
-
-        String p = System.getProperty("sling.feature.apiregions.resource.features.properties");
-        Properties actual = new Properties();
-        actual.load(new FileReader(p));
-
-        Properties expected = new Properties();
-        expected.put("org.sling:somethingelse:1.0.0", "global,internal,private,forbidden");
-        assertEquals(expected, actual);
-    }
-
-    @Test
-    public void testUnrelatedExtension() {
-        BundleArtifactFeatureHandler bafh = new BundleArtifactFeatureHandler();
-        Extension ex = new Extension(ExtensionType.JSON, "foobar", false);
-        bafh.postProcess(null, null, ex);
-        // Should not do anything and definitely not throw an exception
-    }
-
-    private static class TestHandlerContextImpl implements HandlerContext {
-        private final Map<String, String> cfg = new HashMap<>();
-
-        @Override
-        public ArtifactProvider getArtifactProvider() {
-            return null;
-        }
-
-        @Override
-        public Map<String, String> getConfiguration() {
-            return cfg;
-        }
-    }
-}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/BundleMappingHandlerTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/BundleMappingHandlerTest.java
deleted file mode 100644
index 99c2042..0000000
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/BundleMappingHandlerTest.java
+++ /dev/null
@@ -1,160 +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.sling.feature.extension.apiregions;
-
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.builder.ArtifactProvider;
-import org.apache.sling.feature.builder.HandlerContext;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class BundleMappingHandlerTest {
-    @Test
-    public void testHandler() throws IOException {
-        ArtifactProvider ap = new ArtifactProvider() {
-            @Override
-            public URL provide(ArtifactId id) {
-                switch(id.toMvnId()) {
-                case "g:b1:1":
-                    return getResourceFile("b1/b1.jar");
-                case "g:b2:1.2.3":
-                    return getResourceFile("b2/b2.jar");
-                case "g:b3:0":
-                    return getResourceFile("b3/b3.jar");
-                default: return null;
-                }
-            }
-        };
-
-        BundleMappingHandler bmh = new BundleMappingHandler();
-
-        Extension ex = new Extension(ExtensionType.JSON, "api-regions", false);
-        Feature f = new Feature(ArtifactId.fromMvnId("foo:bar:123"));
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("g:b1:1"));
-        f.getBundles().add(b1);
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("g:b2:1.2.3"));
-        f.getBundles().add(b2);
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("g:b3:0"));
-        f.getBundles().add(b3);
-        bmh.postProcess(new TestHandlerContext(ap), f, ex);
-
-        String p = System.getProperty("sling.feature.apiregions.resource.idbsnver.properties");
-        Properties actual = new Properties();
-        actual.load(new FileReader(p));
-
-        Properties expected = new Properties();
-        expected.put("g:b1:1", "b1~1.0.0");
-        expected.put("g:b2:1.2.3", "b2~1.2.3");
-        assertEquals(expected, actual);
-    }
-
-    @Test
-    public void testSpecificDirectory() throws Exception {
-        Path tempDir = Files.createTempDirectory(getClass().getSimpleName());
-
-        try {
-            ArtifactProvider ap = new ArtifactProvider() {
-                @Override
-                public URL provide(ArtifactId id) {
-                    switch(id.toMvnId()) {
-                    case "g:b1:1":
-                        return getResourceFile("b1/b1.jar");
-                    default: return null;
-                    }
-                }
-            };
-
-            Extension ex = new Extension(ExtensionType.JSON, "api-regions", false);
-            Feature f = new Feature(ArtifactId.fromMvnId("foo:bar:123"));
-            Artifact b1 = new Artifact(ArtifactId.fromMvnId("g:b1:1"));
-            f.getBundles().add(b1);
-
-            BundleMappingHandler bmh = new BundleMappingHandler();
-            final Map<String, String> kvm = new HashMap<>();
-            kvm.put("fileStorage", tempDir.toString());
-            bmh.postProcess(new TestHandlerContext(ap,
-                    kvm), f, ex);
-
-            File expectedFile = new File(tempDir.toFile(), "foo_bar_123/idbsnver.properties");
-            assertTrue(expectedFile.exists());
-            Properties p = new Properties();
-            p.load(new FileInputStream(expectedFile));
-
-            Properties ep = new Properties();
-            ep.put("g:b1:1", "b1~1.0.0");
-            assertEquals(ep, p);
-        } finally {
-            for (File f : tempDir.toFile().listFiles()) {
-                f.delete();
-            }
-            tempDir.toFile().delete();
-        }
-    }
-
-    @Test
-    public void testUnrelatedExtension() {
-        BundleMappingHandler bmh = new BundleMappingHandler();
-        Extension ex = new Extension(ExtensionType.JSON, "foobar", false);
-        bmh.postProcess(null, null, ex);
-        // Should not do anything and definitely not throw an exception
-    }
-
-    private URL getResourceFile(String filename) {
-        return getClass().getClassLoader().getResource(filename);
-    }
-
-    private class TestHandlerContext implements HandlerContext {
-        private final ArtifactProvider artifactProvider;
-        private final Map<String, String> config;
-
-        private TestHandlerContext(ArtifactProvider ap, Map<String, String> cfg) {
-            artifactProvider = ap;
-            config = cfg;
-        }
-
-        public TestHandlerContext(ArtifactProvider ap) {
-            this(ap, new HashMap<>());
-        }
-
-        @Override
-        public ArtifactProvider getArtifactProvider() {
-            return artifactProvider;
-        }
-
-        @Override
-        public Map<String, String> getConfiguration() {
-            return config;
-        }
-    }
-}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
index 4600dfc..f080eb2 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -131,7 +132,7 @@ public class CheckApiRegionsBundleExportsImportsTest {
      * different region, bundle 2 is in no region.
      */
     public void testImportExportWithRegionsMissing() throws Exception {
-        String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"]}]";
+        String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":\"f:f:1\"}]";
 
         CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports();
 
@@ -142,15 +143,12 @@ public class CheckApiRegionsBundleExportsImportsTest {
 
         FeatureDescriptor fd = new FeatureDescriptorImpl(f);
 
-        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
-        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar");
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", f.getId());
+        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1"));
 
         AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
         Mockito.when(ctx.getFeature()).thenReturn(f);
         Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
-        Mockito.when(ctx.getConfiguration()).thenReturn(
-                Collections.singletonMap("fileStorage",
-                        resourceRoot + "/origins/testImportExportWithRegionsMissing"));
         t.execute(ctx);
 
         Mockito.verify(ctx).reportError(Mockito.contains("org.foo.b"));
@@ -164,7 +162,12 @@ public class CheckApiRegionsBundleExportsImportsTest {
      * region, bundle 1 is in something region, and bundle 2 is in somethingelse region.
      */
     public void testImportExportWithRegionMismatch() throws Exception {
-        String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"]}]";
+        String exJson =
+            "["
+                + "{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":\"f:f:1\"},"
+                + "{\"name\": \"someotherthing\", \"exports\": [],\"feature-origins\":\"f:f:1\"},"
+                + "{\"name\": \"somethingelse\", \"exports\": [],\"feature-origins\":\"f:f2:1\"}"
+            + "]";
 
         CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports();
 
@@ -175,11 +178,10 @@ public class CheckApiRegionsBundleExportsImportsTest {
 
         FeatureDescriptor fd = new FeatureDescriptorImpl(f);
 
-        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
-        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar");
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", fd.getFeature().getId());
+        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1"));
 
         Map<String, String> cfgMap = new HashMap<String, String>();
-        cfgMap.put("fileStorage", resourceRoot + "/origins/testImportExportWithRegionMismatch");
         cfgMap.put("ignoreAPIRegions", "false");
 
         AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
@@ -202,7 +204,12 @@ public class CheckApiRegionsBundleExportsImportsTest {
      * However this should still pass as the analyzer is configured to ignore regions.
      */
     public void testImportExportWithRegionMismatchIgnoreRegions() throws Exception {
-        String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"]}]";
+        String exJson =
+            "["
+                + "{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":\"f:f:1\"},"
+                + "{\"name\": \"someotherthing\", \"exports\": [],\"feature-origins\":\"f:f:1\"},"
+                + "{\"name\": \"somethingelse\", \"exports\": [],\"feature-origins\":\"f:f2:1\"}"
+                + "]";
 
         CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports();
 
@@ -213,11 +220,10 @@ public class CheckApiRegionsBundleExportsImportsTest {
 
         FeatureDescriptor fd = new FeatureDescriptorImpl(f);
 
-        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
-        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar");
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", fd.getFeature().getId());
+        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1"));
 
         Map<String, String> cfgMap = new HashMap<String, String>();
-        cfgMap.put("fileStorage", resourceRoot + "/origins/testImportExportWithRegionMismatch");
         cfgMap.put("ignoreAPIRegions", "true");
 
         AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
@@ -237,7 +243,12 @@ public class CheckApiRegionsBundleExportsImportsTest {
      * all these bundles are in the same feature they can all see each other.
      */
     public void testImportFromOtherBundleInSameFeature() throws Exception {
-        String exJson = "[{\"name\": \"blah\"}]"; // no exports
+        String exJson = "[{\"name\": \"blah\",\"feature-origins\":\"f:f:2,f:f3:1\"}" +
+            ",{\"name\": \"something\",\"feature-origins\":\"f:f:1\"}" +
+            ",{\"name\": \"someotherthing\",\"feature-origins\":\"f:f:1\"}" +
+            ",{\"name\": \"abc\",\"feature-origins\":\"f:2:1\"}" +
+            ",{\"name\": \"xyz\",\"feature-origins\":\"f:f2:1\"}" +
+            "]"; // no exports
 
         CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports();
 
@@ -248,16 +259,13 @@ public class CheckApiRegionsBundleExportsImportsTest {
 
         FeatureDescriptor fd = new FeatureDescriptorImpl(f);
 
-        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
-        fdAddBundle(fd, "g:b3:1", "test-bundle3.jar");
-        fdAddBundle(fd, "g:b4:1", "test-bundle4.jar");
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", ArtifactId.fromMvnId("f:f3:1"));
+        fdAddBundle(fd, "g:b3:1", "test-bundle3.jar", ArtifactId.fromMvnId("f:f3:1"));
+        fdAddBundle(fd, "g:b4:1", "test-bundle4.jar", ArtifactId.fromMvnId("f:f3:1"));
 
         AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
         Mockito.when(ctx.getFeature()).thenReturn(f);
         Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
-        Mockito.when(ctx.getConfiguration()).thenReturn(
-                Collections.singletonMap("fileStorage",
-                        resourceRoot + "/origins/testImportFromOtherBundleInSameFeature"));
         t.execute(ctx);
 
         Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString());
@@ -270,7 +278,8 @@ public class CheckApiRegionsBundleExportsImportsTest {
      * and bundle 2 imports it in the something region, so this succeeds.
      */
     public void testImportExportWithMatchingRegion() throws Exception {
-        String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"]}]";
+        String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":\"f:f:1,f:f2:1\"}" +
+            ",{\"name\": \"someotherthing\", \"exports\": [\"org.foo.b\"],\"feature-origins\":\"f:f:1\"}]";
 
         CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports();
 
@@ -281,15 +290,12 @@ public class CheckApiRegionsBundleExportsImportsTest {
 
         FeatureDescriptor fd = new FeatureDescriptorImpl(f);
 
-        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
-        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar");
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", f.getId());
+        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1"));
 
         AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
         Mockito.when(ctx.getFeature()).thenReturn(f);
         Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd);
-        Mockito.when(ctx.getConfiguration()).thenReturn(
-                Collections.singletonMap("fileStorage",
-                        resourceRoot + "/origins/testImportExportWithMatchingRegion"));
         t.execute(ctx);
 
         Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString());
@@ -302,7 +308,11 @@ public class CheckApiRegionsBundleExportsImportsTest {
      * Bundle 2 is not explicitly part of the global region, but can still see it
      */
     public void testImportFromGlobalAlwaysSucceeds() throws Exception {
-        String exJson = "[{\"name\": \"global\", \"exports\": [\"org.foo.b\"]}]";
+        String exJson = "[{\"name\": \"global\", \"exports\": [\"org.foo.b\"],\"feature-origins\":\"f:f:1\"}" +
+            ",{\"name\": \"something\", \"exports\": [],\"feature-origins\":\"f:f2:1\"}" +
+            ",{\"name\": \"lalala\", \"exports\": [],\"feature-origins\":\"f:f:1\"}" +
+            ",{\"name\": \"someotherthing\", \"exports\": [],\"feature-origins\":\"f:f:1\"}]"
+            ;
 
         CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports();
 
@@ -313,8 +323,8 @@ public class CheckApiRegionsBundleExportsImportsTest {
 
         FeatureDescriptor fd = new FeatureDescriptorImpl(f);
 
-        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar");
-        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar");
+        fdAddBundle(fd, "g:b1:1", "test-bundle1.jar",f.getId());
+        fdAddBundle(fd, "g:b2:1", "test-bundle2.jar",ArtifactId.fromMvnId("f:f2:1"));
 
         AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class);
         Mockito.when(ctx.getFeature()).thenReturn(f);
@@ -328,9 +338,11 @@ public class CheckApiRegionsBundleExportsImportsTest {
         Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString());
     }
 
-    private void fdAddBundle(FeatureDescriptor fd, String id, String file) throws IOException {
+    private void fdAddBundle(FeatureDescriptor fd, String id, String file, ArtifactId... origins) throws IOException {
+        Artifact artifact = new Artifact(ArtifactId.fromMvnId(id));
+        artifact.setFeatureOrigins(origins);
         BundleDescriptor bd1 = new BundleDescriptorImpl(
-                new Artifact(ArtifactId.fromMvnId(id)), new File(resourceRoot, file).toURI().toURL(), 0);
+                artifact, new File(resourceRoot, file).toURI().toURL(), 0);
         fd.getBundleDescriptors().add(bd1);
     }
 }
diff --git a/src/test/resources/origins/testImportExportWithMatchingRegion/f_f_1/bundleOrigins.properties b/src/test/resources/origins/testImportExportWithMatchingRegion/f_f_1/bundleOrigins.properties
deleted file mode 100644
index 788c4fa..0000000
--- a/src/test/resources/origins/testImportExportWithMatchingRegion/f_f_1/bundleOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-g\:b1\:1=f\:f1\:1
-g\:b2\:1=f\:f2\:1
diff --git a/src/test/resources/origins/testImportExportWithMatchingRegion/f_f_1/regionOrigins.properties b/src/test/resources/origins/testImportExportWithMatchingRegion/f_f_1/regionOrigins.properties
deleted file mode 100644
index ceb9131..0000000
--- a/src/test/resources/origins/testImportExportWithMatchingRegion/f_f_1/regionOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-f\:f1\:1=something,someotherthing
-f\:f2\:1=something
diff --git a/src/test/resources/origins/testImportExportWithRegionMismatch/f_f_1/bundleOrigins.properties b/src/test/resources/origins/testImportExportWithRegionMismatch/f_f_1/bundleOrigins.properties
deleted file mode 100644
index 788c4fa..0000000
--- a/src/test/resources/origins/testImportExportWithRegionMismatch/f_f_1/bundleOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-g\:b1\:1=f\:f1\:1
-g\:b2\:1=f\:f2\:1
diff --git a/src/test/resources/origins/testImportExportWithRegionMismatch/f_f_1/regionOrigins.properties b/src/test/resources/origins/testImportExportWithRegionMismatch/f_f_1/regionOrigins.properties
deleted file mode 100644
index 3134d99..0000000
--- a/src/test/resources/origins/testImportExportWithRegionMismatch/f_f_1/regionOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-f\:f1\:1=something,someotherthing
-f\:f2\:1=somethingelse
diff --git a/src/test/resources/origins/testImportExportWithRegionMismatchIgnoreRegions/f_f_1/bundleOrigins.properties b/src/test/resources/origins/testImportExportWithRegionMismatchIgnoreRegions/f_f_1/bundleOrigins.properties
deleted file mode 100644
index 788c4fa..0000000
--- a/src/test/resources/origins/testImportExportWithRegionMismatchIgnoreRegions/f_f_1/bundleOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-g\:b1\:1=f\:f1\:1
-g\:b2\:1=f\:f2\:1
diff --git a/src/test/resources/origins/testImportExportWithRegionMismatchIgnoreRegions/f_f_1/regionOrigins.properties b/src/test/resources/origins/testImportExportWithRegionMismatchIgnoreRegions/f_f_1/regionOrigins.properties
deleted file mode 100644
index 3134d99..0000000
--- a/src/test/resources/origins/testImportExportWithRegionMismatchIgnoreRegions/f_f_1/regionOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-f\:f1\:1=something,someotherthing
-f\:f2\:1=somethingelse
diff --git a/src/test/resources/origins/testImportExportWithRegionsMissing/f_f_1/bundleOrigins.properties b/src/test/resources/origins/testImportExportWithRegionsMissing/f_f_1/bundleOrigins.properties
deleted file mode 100644
index 788c4fa..0000000
--- a/src/test/resources/origins/testImportExportWithRegionsMissing/f_f_1/bundleOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-g\:b1\:1=f\:f1\:1
-g\:b2\:1=f\:f2\:1
diff --git a/src/test/resources/origins/testImportExportWithRegionsMissing/f_f_1/regionOrigins.properties b/src/test/resources/origins/testImportExportWithRegionsMissing/f_f_1/regionOrigins.properties
deleted file mode 100644
index 019a972..0000000
--- a/src/test/resources/origins/testImportExportWithRegionsMissing/f_f_1/regionOrigins.properties
+++ /dev/null
@@ -1 +0,0 @@
-f\:f1\:1=something
diff --git a/src/test/resources/origins/testImportFromGlobalAlwaysSucceeds/f_f_1/bundleOrigins.properties b/src/test/resources/origins/testImportFromGlobalAlwaysSucceeds/f_f_1/bundleOrigins.properties
deleted file mode 100644
index 788c4fa..0000000
--- a/src/test/resources/origins/testImportFromGlobalAlwaysSucceeds/f_f_1/bundleOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-g\:b1\:1=f\:f1\:1
-g\:b2\:1=f\:f2\:1
diff --git a/src/test/resources/origins/testImportFromGlobalAlwaysSucceeds/f_f_1/regionOrigins.properties b/src/test/resources/origins/testImportFromGlobalAlwaysSucceeds/f_f_1/regionOrigins.properties
deleted file mode 100644
index a94edd0..0000000
--- a/src/test/resources/origins/testImportFromGlobalAlwaysSucceeds/f_f_1/regionOrigins.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-f\:f1\:1=someotherthing,global,lalala
-f\:f2\:1=something
diff --git a/src/test/resources/origins/testImportFromOtherBundleInSameFeature/f_f_2/bundleOrigins.properties b/src/test/resources/origins/testImportFromOtherBundleInSameFeature/f_f_2/bundleOrigins.properties
deleted file mode 100644
index 7910020..0000000
--- a/src/test/resources/origins/testImportFromOtherBundleInSameFeature/f_f_2/bundleOrigins.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-g\:b1\:1=f\:f3\:1
-g\:b2\:1=f\:f3\:1
-g\:b3\:1=f\:f3\:1
-g\:b4\:1=f\:f3\:1
diff --git a/src/test/resources/origins/testImportFromOtherBundleInSameFeature/f_f_2/regionOrigins.properties b/src/test/resources/origins/testImportFromOtherBundleInSameFeature/f_f_2/regionOrigins.properties
deleted file mode 100644
index 3488a06..0000000
--- a/src/test/resources/origins/testImportFromOtherBundleInSameFeature/f_f_2/regionOrigins.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-f\:f1\:1=something,someotherthing
-f\:f2\:1=abc,xyz
-f\:f3\:1=blah