SLING-8642 Specify the target API Region in the Content Package to Feature Model converter
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
index b1e0d4d..390bf57 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/cli/ContentPackage2FeatureModelConverterLauncher.java
@@ -18,6 +18,7 @@
 
 import java.io.File;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 
@@ -76,6 +77,9 @@
     @Option(names = { "-i", "--artifact-id" }, description = "The optional Artifact Id the Feature File will have, once generated; it will be derived, if not specified.", required = false)
     private String artifactIdOverride;
 
+    @Option(names = { "-r", "--api-region" }, description = "The API Regions assigned to the generated features", required = false)
+    private List<String> apiRegions;
+
     @Option(names = {"-D", "--define"}, description = "Define a system property", required = false)
     private Map<String, String> properties = new HashMap<>();
 
@@ -110,12 +114,16 @@
         logger.info("");
 
         try {
+            DefaultFeaturesManager featuresManager = new DefaultFeaturesManager(mergeConfigurations,
+                                                            bundlesStartOrder,
+                                                            featureModelsOutputDirectory,
+                                                            artifactIdOverride,
+                                                            properties);
+            if (apiRegions != null)
+                featuresManager.setAPIRegions(apiRegions);
+
             ContentPackage2FeatureModelConverter converter = new ContentPackage2FeatureModelConverter(strictValidation)
-                                                             .setFeaturesManager(new DefaultFeaturesManager(mergeConfigurations,
-                                                                                                            bundlesStartOrder,
-                                                                                                            featureModelsOutputDirectory,
-                                                                                                            artifactIdOverride,
-                                                                                                            properties))
+                                                             .setFeaturesManager(featuresManager)
                                                              .setBundlesDeployer(new DefaultArtifactsDeployer(artifactsOutputDirectory))
                                                              .setEntryHandlersManager(new DefaultEntryHandlersManager())
                                                              .setAclManager(new DefaultAclManager())
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/features/DefaultFeaturesManager.java b/src/main/java/org/apache/sling/feature/cpconverter/features/DefaultFeaturesManager.java
index 1a16252..f45312f 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/features/DefaultFeaturesManager.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/features/DefaultFeaturesManager.java
@@ -21,10 +21,12 @@
 
 import java.io.File;
 import java.io.FileWriter;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -68,6 +70,8 @@
 
     private final Map<String, String> properties;
 
+    private final List<String> targetAPIRegions = new ArrayList<>();
+
     private Feature targetFeature = null;
 
     public DefaultFeaturesManager() {
@@ -88,9 +92,30 @@
 
     public void init(String groupId, String artifactId, String version) {
         targetFeature = new Feature(new ArtifactId(groupId, artifactId, version, null, SLING_OSGI_FEATURE_TILE_TYPE));
+
+        initAPIRegions();
+
         runModes.clear();
     }
 
+    private void initAPIRegions() {
+        if (targetAPIRegions.size() > 0) {
+            Extension apiRegions = new Extension(ExtensionType.JSON, "api-regions", false);
+            StringBuilder jsonBuilder = new StringBuilder("[");
+            for (String apiRegion : targetAPIRegions) {
+                if (jsonBuilder.length() > 1) {
+                    jsonBuilder.append(',');
+                }
+                jsonBuilder.append("{\"name\":\"");
+                jsonBuilder.append(apiRegion);
+                jsonBuilder.append("\",\"exports\":[]}");
+            }
+            jsonBuilder.append("]");
+            apiRegions.setJSON(jsonBuilder.toString());
+            targetFeature.getExtensions().add(apiRegions);
+        }
+    }
+
     public Feature getTargetFeature() {
         return targetFeature;
     }
@@ -238,4 +263,9 @@
         }
     }
 
+    public synchronized DefaultFeaturesManager setAPIRegions(List<String> regions) {
+        targetAPIRegions.clear();
+        targetAPIRegions.addAll(regions);
+        return this;
+    }
 }
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
index bbe9197..6203af9 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -24,11 +24,16 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileReader;
+import java.io.IOException;
 import java.io.Reader;
+import java.io.StringReader;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
@@ -42,6 +47,7 @@
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Artifacts;
 import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.cpconverter.acl.DefaultAclManager;
 import org.apache.sling.feature.cpconverter.artifacts.DefaultArtifactsDeployer;
@@ -54,15 +60,19 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+
 public class ContentPackage2FeatureModelConverterTest {
 
     /**
      * Test package A-1.0. Depends on B and C-1.X
      * Test package B-1.0. Depends on C
      */
-    private static String[] TEST_PACKAGES_INPUT = { "test_c-1.0.zip", "test_a-1.0.zip", "test_b-1.0.zip" }; 
+    private static String[] TEST_PACKAGES_INPUT = { "test_c-1.0.zip", "test_a-1.0.zip", "test_b-1.0.zip" };
 
-    private static String[] TEST_PACKAGES_OUTPUT = { "my_packages:test_c:1.0", "my_packages:test_b:1.0", "my_packages:test_a:1.0" }; 
+    private static String[] TEST_PACKAGES_OUTPUT = { "my_packages:test_c:1.0", "my_packages:test_b:1.0", "my_packages:test_a:1.0" };
 
     private static String[] TEST_PACKAGES_CYCLIC_DEPENDENCY = { "test_d-1.0.zip",
                                                                 "test_c-1.0.zip",
@@ -196,7 +206,7 @@
                              "jcr_root/config.xml",
                              "jcr_root/definition/.content.xml");
     }
-    
+
     @Test
     public void convertContentPackageDropContent() throws Exception {
         URL packageUrl = getClass().getResource("test-content-package.zip");
@@ -267,6 +277,53 @@
         assertFalse(new File(outputDirectory, "asd/sample/asd.retail.all/0.0.1/asd.retail.all-0.0.1-cp2fm-converted.zip").exists());
     }
 
+    @Test
+    public void testConvertContentPackageWithAPIRegion() throws Exception {
+        URL cp = getClass().getResource("test_c-1.0.zip");
+        File cpFile = new File(cp.getFile());
+        File outDir = Files.createTempDirectory(getClass().getSimpleName()).toFile();
+
+        try {
+            DefaultFeaturesManager fm = new DefaultFeaturesManager(true, 5, outDir, null, null);
+            fm.setAPIRegions(Arrays.asList("global", "foo.bar"));
+            converter.setFeaturesManager(fm)
+                     .setBundlesDeployer(new DefaultArtifactsDeployer(outDir))
+                     .setEmitter(DefaultPackagesEventsEmitter.open(outDir))
+                     .convert(cpFile);
+
+            File featureFile = new File(outDir, "test_c.json");
+            try (Reader reader = new FileReader(featureFile)) {
+                Feature feature = FeatureJSONReader.read(reader, featureFile.getAbsolutePath());
+
+                Extension apiRegions = feature.getExtensions().getByName("api-regions");
+                assertEquals(ExtensionType.JSON, apiRegions.getType());
+                String json = apiRegions.getJSON();
+                JsonArray ja = Json.createReader(new StringReader(json)).readArray();
+                assertEquals(2, ja.size());
+
+                JsonObject globalJO = ja.getJsonObject(0);
+                assertEquals("global", globalJO.getString("name"));
+                assertEquals(0, globalJO.getJsonArray("exports").size());
+
+                JsonObject foobarJO = ja.getJsonObject(1);
+                assertEquals("foo.bar", foobarJO.getString("name"));
+                assertEquals(0, foobarJO.getJsonArray("exports").size());
+            }
+
+        } finally {
+            deleteDirTree(outDir);
+        }
+    }
+
+    private void deleteDirTree(File dir) throws IOException {
+        Path tempDir = dir.toPath();
+
+        Files.walk(tempDir)
+            .sorted(Comparator.reverseOrder())
+            .map(Path::toFile)
+            .forEach(File::delete);
+    }
+
     private void verifyFeatureFile(File outputDirectory,
                                    String name,
                                    String expectedArtifactId,
@@ -471,18 +528,18 @@
 
             String expected = "register nodetypes\n" +
                     "<<===\n" +
-                    "<< <'sling'='http://sling.apache.org/jcr/sling/1.0'>\n" + 
-                    "<< <'nt'='http://www.jcp.org/jcr/nt/1.0'>\n" + 
-                    "<< <'rep'='internal'>\n" + 
-                    "\n" + 
-                    "<< [sling:Folder] > nt:folder\n" + 
-                    "<<   - * (undefined) multiple\n" + 
-                    "<<   - * (undefined)\n" + 
-                    "<<   + * (nt:base) = sling:Folder version\n" + 
-                    "\n" + 
-                    "<< [rep:RepoAccessControllable]\n" + 
-                    "<<   mixin\n" + 
-                    "<<   + rep:repoPolicy (rep:Policy) protected ignore\n" + 
+                    "<< <'sling'='http://sling.apache.org/jcr/sling/1.0'>\n" +
+                    "<< <'nt'='http://www.jcp.org/jcr/nt/1.0'>\n" +
+                    "<< <'rep'='internal'>\n" +
+                    "\n" +
+                    "<< [sling:Folder] > nt:folder\n" +
+                    "<<   - * (undefined) multiple\n" +
+                    "<<   - * (undefined)\n" +
+                    "<<   + * (nt:base) = sling:Folder version\n" +
+                    "\n" +
+                    "<< [rep:RepoAccessControllable]\n" +
+                    "<<   mixin\n" +
+                    "<<   + rep:repoPolicy (rep:Policy) protected ignore\n" +
                     "\n===>>\n";
             String actual = repoinitExtension.getText();
             assertEquals(expected, actual);