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/15 16:28:35 UTC

[sling-org-apache-sling-feature] branch master updated: SLING-8998: Add feature-origins metadata entry to artifacts in featur… (#17)

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

pauls pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git


The following commit(s) were added to refs/heads/master by this push:
     new 3ddbbf1  SLING-8998: Add feature-origins metadata entry to artifacts in featur… (#17)
3ddbbf1 is described below

commit 3ddbbf1572d6096bc99c348db58c3136eff0570f
Author: Karl Pauls <pa...@apache.org>
AuthorDate: Wed Jan 15 17:28:28 2020 +0100

    SLING-8998: Add feature-origins metadata entry to artifacts in featur… (#17)
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
    
    * SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
---
 .../java/org/apache/sling/feature/Artifact.java    |  40 ++++++
 .../apache/sling/feature/builder/BuilderUtil.java  |  60 ++++++--
 .../sling/feature/builder/FeatureBuilder.java      |   6 +
 .../sling/feature/builder/BuilderUtilTest.java     |  11 ++
 .../sling/feature/builder/FeatureBuilderTest.java  | 153 ++++++++++++++-------
 5 files changed, 215 insertions(+), 55 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/Artifact.java b/src/main/java/org/apache/sling/feature/Artifact.java
index c1d5523..3f5ed3e 100644
--- a/src/main/java/org/apache/sling/feature/Artifact.java
+++ b/src/main/java/org/apache/sling/feature/Artifact.java
@@ -16,10 +16,15 @@
  */
 package org.apache.sling.feature;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * An artifact consists of
@@ -39,6 +44,8 @@ public class Artifact implements Comparable<Artifact> {
     /** This key might be used by bundles to define the start order. */
     public static final String KEY_START_ORDER = "start-order";
 
+    public static final String KEY_FEATURE_ORIGINS = "feature-origins";
+
     /** The artifact id. */
     private final ArtifactId id;
 
@@ -139,6 +146,39 @@ public class Artifact implements Comparable<Artifact> {
         }
     }
 
+    public ArtifactId[] getFeatureOrigins() {
+        String origins = this.getMetadata().get(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.getMetadata().put(KEY_FEATURE_ORIGINS, origins);
+        }
+        else {
+            this.getMetadata().remove(KEY_FEATURE_ORIGINS);
+        }
+    }
+
     @Override
     public int compareTo(final Artifact o) {
         return this.id.compareTo(o.id);
diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
index febf000..d723084 100644
--- a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
@@ -19,6 +19,7 @@ package org.apache.sling.feature.builder;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -166,7 +167,7 @@ class BuilderUtil {
                     selectedArtifacts.add(count++, existing);
                     selectedArtifacts.add(artifactFromSource);
                 } else {
-                    List<Artifact> artifacts = selectArtifactOverride(existing, artifactFromSource, artifactOverrides);
+                    List<Artifact> artifacts = selectArtifactOverride(existing, artifactFromSource, artifactOverrides, sourceFeature.getId());
                     // if we have an all policy we might have more then one artifact - we put the target one first
                     if (artifacts.size() > 1) {
                         selectedArtifacts.add(count++, artifacts.remove(0));
@@ -187,7 +188,7 @@ class BuilderUtil {
 
             for (final Artifact sa : new LinkedHashSet<>(selectedArtifacts)) {
                 // create a copy to detach artifact from source
-                final Artifact cp = sa.copy(sa.getId());
+                final Artifact cp = addFeatureOrigin(sourceFeature, sa.copy(sa.getId()));
                 // Record the original feature of the bundle, if needed
                 if (originKey != null) {
                     if (sourceFeature != null && source.contains(sa) && sa.getMetadata().get(originKey) == null) {
@@ -206,10 +207,15 @@ class BuilderUtil {
     }
 
     static List<Artifact> selectArtifactOverride(Artifact fromTarget, Artifact fromSource,
-            List<ArtifactId> artifactOverrides) {
+        List<ArtifactId> artifactOverrides) {
+        return selectArtifactOverride(fromTarget, fromSource, artifactOverrides, null);
+    }
+
+    static List<Artifact> selectArtifactOverride(Artifact fromTarget, Artifact fromSource,
+            List<ArtifactId> artifactOverrides, ArtifactId sourceID) {
         if (fromTarget.getId().equals(fromSource.getId())) {
             // They're the same so return the source (latest)
-            return Collections.singletonList(selectStartOrder(fromTarget, fromSource, fromSource));
+            return Collections.singletonList(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromSource), fromTarget, fromSource));
         }
 
         final Set<ArtifactId> commonPrefixes = getCommonArtifactIds(fromTarget, fromSource);
@@ -219,6 +225,11 @@ class BuilderUtil {
         }
 
         final List<Artifact> result = new ArrayList<>();
+        if (artifactOverrides.isEmpty() && Arrays.asList(fromTarget.getFeatureOrigins()).contains(sourceID)) {
+            result.add(fromTarget);
+            result.add(addFeatureOrigin(fromSource, fromTarget, fromSource));
+            return result;
+        }
         outer: for (ArtifactId prefix : commonPrefixes) {
             for (final ArtifactId override : artifactOverrides) {
                 if (match(prefix, override)) {
@@ -230,21 +241,24 @@ class BuilderUtil {
                     } else if (BuilderContext.VERSION_OVERRIDE_HIGHEST.equalsIgnoreCase(rule)) {
                         Version a1v = fromTarget.getId().getOSGiVersion();
                         Version a2v = fromSource.getId().getOSGiVersion();
-                        result.add(selectStartOrder(fromTarget, fromSource, a1v.compareTo(a2v) > 0 ? fromTarget : fromSource));
+                        result.add(
+                            addFeatureOrigin(
+                                selectStartOrder(fromTarget, fromSource, a1v.compareTo(a2v) > 0 ? fromTarget : fromSource),
+                                fromTarget, fromSource));
                     } else if (BuilderContext.VERSION_OVERRIDE_LATEST.equalsIgnoreCase(rule)) {
-                        result.add(selectStartOrder(fromTarget, fromSource, fromSource));
+                        result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromSource), fromTarget, fromSource));
                     } else {
 
                         // The rule must represent a version
                         // See if its one of the existing artifact. If so use those, as they may have
                         // additional metadata
                         if (fromTarget.getId().getVersion().equals(rule)) {
-                            result.add(selectStartOrder(fromTarget, fromSource, fromTarget));
+                            result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromTarget), fromTarget, fromSource));
                         } else if (fromSource.getId().getVersion().equals(rule)) {
-                            result.add(selectStartOrder(fromTarget, fromSource, fromSource));
+                            result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromSource), fromTarget, fromSource));
                         } else {
                             // It's a completely new artifact
-                            result.add(selectStartOrder(fromTarget, fromSource, new Artifact(override)));
+                            result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, new Artifact(override)), fromTarget, fromSource));
                         }
                     }
                     break outer;
@@ -282,6 +296,34 @@ class BuilderUtil {
         }
     }
 
+    private static Artifact addFeatureOrigin(Artifact target, Artifact... artifacts) {
+        return addFeatureOrigin(null, target, artifacts);
+    }
+
+    private static Artifact addFeatureOrigin(Feature feature, Artifact target, Artifact... artifacts) {
+        LinkedHashSet<ArtifactId> originFeatures = new LinkedHashSet<>();
+        if (artifacts != null && artifacts.length > 0) {
+            for (Artifact artifact : artifacts) {
+                originFeatures.addAll(Arrays.asList(artifact.getFeatureOrigins()));
+            }
+        }
+        else {
+            originFeatures.addAll(Arrays.asList(target.getFeatureOrigins()));
+        }
+        if (feature != null) {
+            originFeatures.add(feature.getId());
+        }
+        ArtifactId[] origins = originFeatures.toArray(new ArtifactId[0]);
+        if (Arrays.equals(origins,target.getFeatureOrigins())) {
+            return target;
+        }
+        else {
+            Artifact result = target.copy(target.getId());
+            result.setFeatureOrigins(origins);
+            return result;
+        }
+    }
+
     private static boolean match(final ArtifactId id, final ArtifactId override) {
         int matchCount = 0;
         // check group id
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
index 83de762..cbae43e 100644
--- a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
@@ -17,9 +17,11 @@
 package org.apache.sling.feature.builder;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -334,6 +336,10 @@ public abstract class FeatureBuilder {
 
             for (Artifact a : result.getBundles()) {
                 a.getMetadata().remove(TRACKING_KEY);
+                LinkedHashSet<ArtifactId> originList = new LinkedHashSet<>(Arrays.asList(a.getFeatureOrigins()));
+                originList.remove(prototypeFeature.getId());
+                originList.add(feature.getId());
+                a.setFeatureOrigins(originList.toArray(new ArtifactId[0]));
             }
             for (Extension e : result.getExtensions()) {
                 if (ExtensionType.ARTIFACTS == e.getType()) {
diff --git a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
index e4f41a7..b132e99 100644
--- a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
@@ -790,4 +790,15 @@ public class BuilderUtilTest {
 
         return a;
     }
+
+    @SafeVarargs
+    public static Artifact createBundle(final String id, Map.Entry<String, String> ... metadata) {
+        final Artifact a = new Artifact(ArtifactId.parse(id));
+
+        for (Map.Entry<String, String> md : metadata) {
+            a.getMetadata().put(md.getKey(), md.getValue());
+        }
+
+        return a;
+    }
 }
diff --git a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
index 53ed1ce..849230a 100644
--- a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sling.feature.builder;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -23,6 +24,7 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.AbstractMap;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -211,7 +213,7 @@ public class FeatureBuilderTest {
         b.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 10));
 
         Feature ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 8));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId() + "," + b.getId())));
 
         Feature assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
                 .addArtifactsOverride(ArtifactId.fromMvnId("o:a:HIGHEST")), a, b);
@@ -219,9 +221,6 @@ public class FeatureBuilderTest {
 
         equals(ab, assembled );
 
-        ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 8));
-
         assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
             .addArtifactsOverride(ArtifactId.fromMvnId("o:a:LATEST")), a, b);
         assembled.getExtensions().clear();
@@ -229,8 +228,12 @@ public class FeatureBuilderTest {
         equals(ab, assembled );
 
         ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().addAll(a.getBundles());
-        ab.getBundles().addAll(b.getBundles());
+        for (Artifact bundle : a.getBundles()) {
+            ab.getBundles().add(BuilderUtilTest.createBundle(bundle.getId().toMvnId(), bundle.getStartOrder(), new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId() + "," + b.getId())));
+        }
+        for (Artifact bundle : b.getBundles()) {
+            ab.getBundles().add(BuilderUtilTest.createBundle(bundle.getId().toMvnId(), bundle.getStartOrder(), new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId())));
+        }
 
         assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
             .addArtifactsOverride(ArtifactId.fromMvnId("o:a:ALL")), a, b);
@@ -242,7 +245,7 @@ public class FeatureBuilderTest {
         a.getBundles().get(1).setStartOrder(1);
 
         ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 1));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 1, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId() + "," + b.getId())));
         ab.getBundles().get(0).setStartOrder(1);
 
         assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
@@ -253,6 +256,59 @@ public class FeatureBuilderTest {
         equals(ab, assembled);
     }
 
+    @Test public void testMergeMultipleVersionsNoConflict() {
+        Feature a = new Feature(ArtifactId.fromMvnId("g:a:1"));
+        Feature b = new Feature(ArtifactId.fromMvnId("g:b:1"));
+
+
+        a.getBundles().add(BuilderUtilTest.createBundle("o/a/1.0.0", 10));
+        a.getBundles().add(BuilderUtilTest.createBundle("o/a/2.0.0", 9));
+        a.getBundles().add(BuilderUtilTest.createBundle("o/a/3.0.0", 11));
+
+        b.getBundles().add(BuilderUtilTest.createBundle("o/b/4.0.0", 8));
+        b.getBundles().add(BuilderUtilTest.createBundle("o/b/5.0.0", 12));
+        b.getBundles().add(BuilderUtilTest.createBundle("o/b/6.0.0", 10));
+
+        Feature ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/1.0.0", 10, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/2.0.0", 9, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/3.0.0", 11, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId().toMvnId())));
+
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/b/4.0.0", 8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/b/5.0.0", 12, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/b/6.0.0", 10, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId())));
+
+
+        Feature assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider), a, b);
+        assembled.getExtensions().clear();
+
+        equals(ab, assembled);
+
+        assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:2"), new BuilderContext(provider), assembled);
+        assembled.getExtensions().clear();
+
+        Feature ab2 = new Feature(ArtifactId.fromMvnId("g:ab:2"));
+        ab2.getBundles().add(BuilderUtilTest.createBundle("o/a/1.0.0", 10, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId().toMvnId() + "," + ab.getId().toMvnId())));
+        ab2.getBundles().add(BuilderUtilTest.createBundle("o/a/2.0.0", 9, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId().toMvnId() + "," + ab.getId().toMvnId())));
+        ab2.getBundles().add(BuilderUtilTest.createBundle("o/a/3.0.0", 11, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, a.getId().toMvnId() + "," + ab.getId().toMvnId())));
+
+        ab2.getBundles().add(BuilderUtilTest.createBundle("o/b/4.0.0", 8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId() + "," + ab.getId().toMvnId())));
+        ab2.getBundles().add(BuilderUtilTest.createBundle("o/b/5.0.0", 12, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId() + "," + ab.getId().toMvnId())));
+        ab2.getBundles().add(BuilderUtilTest.createBundle("o/b/6.0.0", 10, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, b.getId().toMvnId() + "," + ab.getId().toMvnId())));
+
+        equals(ab2, assembled);
+    }
+
+    @Test public void testDistinctOrigions() {
+        Artifact a = BuilderUtilTest.createBundle("a/b/1.0.0", 12, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, "b/b/1.0.0,b/b/1.0.0,,b/b/2.0.0"));
+        Artifact b = BuilderUtilTest.createBundle("b/b/2.0.0", 12, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, "b/b/1.0.0,b/b/2.0.0"));
+        assertArrayEquals(a.getFeatureOrigins(), b.getFeatureOrigins());
+
+        a.setFeatureOrigins(new ArtifactId[]{ArtifactId.parse("b/b/1.0.0"), null, ArtifactId.parse("b/b/1.0.0"), ArtifactId.parse("b/b/2.0.0")});
+
+        assertArrayEquals(a.getFeatureOrigins(), b.getFeatureOrigins());
+    }
+
 
     @Test public void testNoIncludesNoUpgrade() throws Exception {
         final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
@@ -353,18 +409,20 @@ public class FeatureBuilderTest {
         result.setPrototype(null);
         result.getBundles().clear();
 
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/2", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/2", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/1", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10));
-        result.getBundles().add(a1.copy(a1.getId()));
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/2", 5, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/2", 5, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 5, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/1", 5, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS,base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        Artifact copy = a1.copy(a1.getId());
+        copy.getMetadata().put(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId());
+        result.getBundles().add(copy);
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
 
         result.getVariables().put("varx", "myvalx");
         result.getFrameworkProperties().put("bar", "X");
@@ -392,24 +450,34 @@ public class FeatureBuilderTest {
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
 
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
-
-
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b1);
 
 
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        Artifact b2 = BuilderUtilTest.createBundle("group:testmulti:3", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b2);
 
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         b3.setStartOrder(4);
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b0);
 
 
         equals(result, assembled);
+
+        Feature addOn = new Feature(ArtifactId.fromMvnId("g:addon:1"));
+        addOn.getBundles().add(BuilderUtilTest.createBundle("group:someart:1.2.3"));
+        assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:tgtart:2"), builderContext, assembled, addOn);
+        assembled.getExtensions().clear();
+
+        result = result.copy(ArtifactId.fromMvnId("g:tgtart:2"));
+        int idx = result.getBundles().indexOf(BuilderUtilTest.createBundle("group:someart:1.2.3"));
+        result.getBundles().remove(idx);
+        result.getBundles().add(idx, BuilderUtilTest.createBundle("group:someart:1.2.3", 4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId() + "," + addOn.getId())));
+
+        equals(result, assembled);
     }
 
     @Test public void testSingleIncludeMultiVersion2() {
@@ -422,16 +490,13 @@ public class FeatureBuilderTest {
         Feature assembled = FeatureBuilder.assemble(base, builderContext);
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
-        b1.setStartOrder(4);
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1" ,4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b1);
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:2"));
-        b2.setStartOrder(8);
+        Artifact b2 = BuilderUtilTest.createBundle("group:testmulti:2",8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b2);
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
-        b3.setStartOrder(4);
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3",4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b0);
 
         equals(result, assembled);
@@ -449,15 +514,13 @@ public class FeatureBuilderTest {
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
 
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
 
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
-        b1.setStartOrder(4);
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1", 4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b1);
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
-        b3.setStartOrder(4);
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3", 4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b0);
 
         equals(result, assembled);
@@ -476,19 +539,17 @@ public class FeatureBuilderTest {
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
 
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId())));
 
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
-        b1.setStartOrder(4);
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1",4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b1);
 
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        Artifact b2 = BuilderUtilTest.createBundle("group:testmulti:3", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b2);
 
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
-        b3.setStartOrder(4);
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3", 4, new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry(Artifact.KEY_FEATURE_ORIGINS, base.getId().toMvnId()));
         result.getBundles().add(b0);
 
         equals(result, assembled);