SLING-10298 : Record origins for configurations and bundles
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
index 84bc7ac..0a277b1 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -23,6 +23,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -88,7 +89,7 @@
 
     private final RecollectorVaultPackageScanner recollectorVaultPackageScanner;
 
-    private PackagesEventsEmitter emitter;
+    private List<PackagesEventsEmitter> emitters = new ArrayList<>();
 
     private boolean failOnMixedPackages = false;
 
@@ -123,6 +124,9 @@
 
     public @NotNull ContentPackage2FeatureModelConverter setFeaturesManager(@Nullable FeaturesManager featuresManager) {
         this.featuresManager = featuresManager;
+        if ( featuresManager instanceof PackagesEventsEmitter ) {
+            this.emitters.add((PackagesEventsEmitter)featuresManager);
+        }
         return this;
     }
 
@@ -154,7 +158,7 @@
     }
 
     public @NotNull ContentPackage2FeatureModelConverter setEmitter(@Nullable PackagesEventsEmitter emitter) {
-        this.emitter = emitter;
+        this.emitters.add(emitter);
         return this;
     }
     
@@ -229,11 +233,11 @@
     }
 
     protected void secondPass(@NotNull Collection<VaultPackage> orderedContentPackages) throws Exception {
-        emitter.start();
+        emitters.stream().forEach(e -> e.start());
 
         for (VaultPackage vaultPackage : orderedContentPackages) {
             try {
-                emitter.startPackage(vaultPackage);
+                emitters.stream().forEach(e -> e.startPackage(vaultPackage));
                 mainPackageAssembler = VaultPackageAssembler.create(this.getTempDirectory(), vaultPackage, removeInstallHooks);
                 assemblers.add(mainPackageAssembler);
 
@@ -264,7 +268,7 @@
                 logger.info("Conversion complete!");
 
                 featuresManager.serialize();
-                emitter.endPackage();
+                emitters.stream().forEach(e -> e.endPackage());
             } finally {
                 aclManager.reset();
                 assemblers.clear();
@@ -279,7 +283,7 @@
 
         mutableContentsIds.clear();
 
-        emitter.end();
+        emitters.stream().forEach(e -> e.end());
     }
 
     private void orderDependencies(@NotNull Map<PackageId, VaultPackage> idFileMap,
@@ -312,7 +316,7 @@
             return;
         }
 
-        emitter.startSubPackage(path, vaultPackage);
+        emitters.stream().forEach(e -> e.startSubPackage(path, vaultPackage));
 
         PackageId originalPackageId = vaultPackage.getId();
         ArtifactId mvnPackageId = toArtifactId(vaultPackage);
@@ -347,7 +351,7 @@
         // restore the previous assembler
         mainPackageAssembler = handler;
 
-        emitter.endSubPackage();
+        emitters.stream().forEach(e -> e.endSubPackage());
     }
 
     private void processContentPackageArchive(@NotNull File contentPackageArchive,
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 d0df8c5..b16425c 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
@@ -25,6 +25,7 @@
 import java.util.*;
 import java.util.Map.Entry;
 
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Artifacts;
@@ -38,6 +39,7 @@
 import org.apache.sling.feature.cpconverter.accesscontrol.Mapping;
 import org.apache.sling.feature.cpconverter.interpolator.SimpleVariablesInterpolator;
 import org.apache.sling.feature.cpconverter.interpolator.VariablesInterpolator;
+import org.apache.sling.feature.cpconverter.vltpkg.PackagesEventsEmitter;
 import org.apache.sling.feature.extension.apiregions.api.ApiExport;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegion;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
@@ -49,7 +51,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class DefaultFeaturesManager implements FeaturesManager {
+public class DefaultFeaturesManager implements FeaturesManager, PackagesEventsEmitter {
 
     public enum ConfigurationHandling {
         ORDERED,
@@ -63,6 +65,9 @@
 
     private static final String JSON_FILE_EXTENSION = ".json";
 
+    private static final String BUNDLE_ORIGINS = "content-package-origins";
+    private static final String CONFIGURATION_ORIGINS = Configuration.CONFIGURATOR_PREFIX.concat(BUNDLE_ORIGINS);
+
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final Map<String, Feature> runModes = new HashMap<>();
@@ -93,6 +98,8 @@
 
     private final Map<String, String> pidToPathMapping = new HashMap<>();
 
+    private final Stack<String> packageIds = new Stack<>();
+    
     DefaultFeaturesManager() {
         this(new File(""));
     }
@@ -183,9 +190,15 @@
 
             artifacts = extension.getArtifacts();
         } else {
-            int startOrderForBundle = startOrder != null ? startOrder.intValue() : bundlesStartOrder;
+            // set start order
+            final int startOrderForBundle = startOrder != null ? startOrder.intValue() : bundlesStartOrder;
             artifact.setStartOrder(startOrderForBundle);
-            artifacts = targetFeature.getBundles();
+            // set origins
+            if ( !this.packageIds.isEmpty() ) {
+                artifact.getMetadata().put(BUNDLE_ORIGINS, String.join("|", this.packageIds));
+            }
+
+            artifacts = targetFeature.getBundles();        
         }
 
         artifacts.add(artifact);
@@ -288,7 +301,6 @@
         @NotNull String path,
         @NotNull Dictionary<String, Object> configurationProperties) {
         String factoryPid = null;
-        String id;
         int n = pid.indexOf('~');
         if (n > 0) {
             factoryPid = pid.substring(0, n);
@@ -339,6 +351,17 @@
         configuration.getProperties().remove(Constants.SERVICE_PID);
         configuration.getProperties().remove("service.bundleLocation");
         configuration.getProperties().remove("service.factoryPid");
+
+        // set origins
+        if ( !this.packageIds.isEmpty() ) {
+            final List<String> origins = new ArrayList<>();
+            final Object val = configuration.getProperties().get(CONFIGURATION_ORIGINS);
+            if ( val != null ) {
+                origins.addAll(Arrays.asList(val.toString().split(",")));
+            }    
+            origins.add(String.join("|", this.packageIds));
+            configuration.getProperties().put(CONFIGURATION_ORIGINS, String.join(",", origins));
+        }
     }
 
     private void addAPIRegions(@NotNull Feature feature, @Nullable List<String> exportedPackages) throws IOException {
@@ -462,4 +485,49 @@
             }
         }
     }
+
+    /**
+     * Package converter starts
+     */
+    public void start() {
+        // nothing to do
+    }
+
+    /** 
+     * Package converter ends
+     */
+    public void end() {
+        // nothing to do
+    }
+
+    /**
+     * Package starts
+     * @param vaultPackage the package
+     */
+    public void startPackage(final @NotNull VaultPackage vaultPackage) {
+        packageIds.push(vaultPackage.getId().toString());
+    }
+
+    /**
+     * Package ends
+     */
+    public void endPackage() {
+        packageIds.pop();
+    }
+
+    /**
+     * Sub package starts
+     * @param path The path
+     * @param vaultPackage the package
+     */
+    public void startSubPackage(final @NotNull String path, final @NotNull VaultPackage vaultPackage) {
+        packageIds.push(vaultPackage.getId().toString());
+    }
+
+    /**
+     * Sub package ends
+     */
+    public void endSubPackage() {
+        packageIds.pop();
+    }
 }