You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by si...@apache.org on 2019/05/24 12:42:01 UTC

[sling-org-apache-sling-feature-cpconverter] 01/02: SLING-8433 - [cp2fm] handle same sub-content-package multi-versions

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

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

commit 6ec4ee04777d59975f764548c29b7384b2a667ac
Author: Simo Tripodi <st...@adobe.com>
AuthorDate: Fri May 24 14:32:47 2019 +0200

    SLING-8433 - [cp2fm] handle same sub-content-package multi-versions
---
 pom.xml                                            |  10 --
 .../ContentPackage2FeatureModelConverter.java      | 124 +++++++++++----------
 .../features/DefaultFeaturesManager.java           |   6 +-
 .../cpconverter/features/FeaturesManager.java      |   5 +-
 .../handlers/AbstractContentPackageHandler.java    |  66 +++++++++++
 .../handlers/ContentPackageEntryHandler.java       |  33 +-----
 .../VersionResolverContentPackageEntryHandler.java |  82 ++++++++++++++
 .../vltpkg/BaseVaultPackageScanner.java            |  39 +++++--
 .../vltpkg/RecollectorVaultPackageScanner.java     |  50 +++++++++
 .../cpconverter/vltpkg/VaultPackageAssembler.java  |   2 +-
 .../ContentPackage2FeatureModelConverterTest.java  |  17 ++-
 .../handlers/BundleEntryHandlerTest.java           |   2 +-
 .../feature/cpconverter/test-content-package-2.zip | Bin 0 -> 56187 bytes
 13 files changed, 317 insertions(+), 119 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7f7acd2..fda28c6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -233,16 +233,6 @@
       </plugin>
 
       <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <systemPropertyVariables>
-            <testDirectory>${project.build.directory}/unit-tests</testDirectory>
-          </systemPropertyVariables>
-        </configuration>
-      </plugin>
-
-      <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>license-maven-plugin</artifactId>
         <version>1.16</version>
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 c25f819..f8c97ee 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverter.java
@@ -25,16 +25,15 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
 import org.apache.jackrabbit.vault.packaging.CyclicDependencyException;
 import org.apache.jackrabbit.vault.packaging.Dependency;
 import org.apache.jackrabbit.vault.packaging.PackageId;
-import org.apache.jackrabbit.vault.packaging.PackageManager;
 import org.apache.jackrabbit.vault.packaging.PackageProperties;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
-import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
 import org.apache.sling.feature.cpconverter.acl.AclManager;
 import org.apache.sling.feature.cpconverter.artifacts.ArtifactsDeployer;
 import org.apache.sling.feature.cpconverter.artifacts.FileArtifactWriter;
@@ -43,9 +42,8 @@ import org.apache.sling.feature.cpconverter.filtering.ResourceFilter;
 import org.apache.sling.feature.cpconverter.handlers.EntryHandler;
 import org.apache.sling.feature.cpconverter.handlers.EntryHandlersManager;
 import org.apache.sling.feature.cpconverter.vltpkg.BaseVaultPackageScanner;
+import org.apache.sling.feature.cpconverter.vltpkg.RecollectorVaultPackageScanner;
 import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanner {
 
@@ -55,9 +53,7 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
 
     private static final String DEFEAULT_VERSION = "0.0.0";
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final PackageManager packageManager = new PackageManagerImpl();
+    private final Map<PackageId, String> subContentPackages = new HashMap<>();
 
     private EntryHandlersManager handlersManager;
 
@@ -71,12 +67,15 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
 
     private VaultPackageAssembler mainPackageAssembler = null;
 
+    private RecollectorVaultPackageScanner recollectorVaultPackageScanner;
+
     public ContentPackage2FeatureModelConverter() {
         this(false);
     }
 
     public ContentPackage2FeatureModelConverter(boolean strictValidation) {
         super(strictValidation);
+        this.recollectorVaultPackageScanner = new RecollectorVaultPackageScanner(this, this.packageManager, strictValidation, subContentPackages);
     }
 
     public ContentPackage2FeatureModelConverter setEntryHandlersManager(EntryHandlersManager handlersManager) {
@@ -122,38 +121,68 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
 
     public void convert(File...contentPackages) throws Exception {
         requireNonNull(contentPackages , "Null content-package(s) can not be converted.");
+        secondPass(firstPass(contentPackages));
+    }
+
+    protected Collection<VaultPackage> firstPass(File...contentPackages) throws Exception {
+        Map<PackageId, VaultPackage> idFileMap = new LinkedHashMap<>();
+        Map<PackageId, VaultPackage> idPackageMapping = new ConcurrentHashMap<>();
+
+        for (File contentPackage : contentPackages) {
+            requireNonNull(contentPackage, "Null content-package can not be converted.");
+
+            if (!contentPackage.exists() || !contentPackage.isFile()) {
+                throw new IllegalArgumentException("File " + contentPackage + " does not exist or it is a directory");
+            }
+
+            logger.info("Reading content-package '{}'...", contentPackage);
 
-        Collection<VaultPackage> orderedContentPackages = firstPass(contentPackages);
+            VaultPackage pack = open(contentPackage);
+            idPackageMapping.put(pack.getId(), pack);
 
+            // analyze sub-content packages in order to filter out
+            // possible outdated conflictring packages
+            recollectorVaultPackageScanner.traverse(pack);
+
+            logger.info("content-package '{}' successfully read!", contentPackage);
+        }
+
+        logger.info("Ordering input content-package(s) {}...", idPackageMapping.keySet());
+
+        for (VaultPackage pack : idPackageMapping.values()) {
+            orderDependencies(idFileMap, idPackageMapping, pack, new HashSet<PackageId>());
+        }
+
+        logger.info("New content-package(s) order: {}", idFileMap.keySet());
+
+        return idFileMap.values();
+    }
+
+    protected void secondPass(Collection<VaultPackage> orderedContentPackages) throws Exception {
         for (VaultPackage vaultPackage : orderedContentPackages) {
             try {
                 mainPackageAssembler = VaultPackageAssembler.create(vaultPackage);
-                PackageProperties packageProperties = vaultPackage.getProperties();
+                PackageId packageProperties = vaultPackage.getId();
 
-                String group = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_GROUP),
+                String group = requireNonNull(packageProperties.getGroup(),
                                               PackageProperties.NAME_GROUP
                                               + " property not found in content-package "
                                               + vaultPackage
                                               + ", please check META-INF/vault/properties.xml")
                                               .replace('/', '.');
 
-                String name = requireNonNull(packageProperties.getProperty(PackageProperties.NAME_NAME),
+                String name = requireNonNull(packageProperties.getName(),
                                             PackageProperties.NAME_NAME
                                             + " property not found in content-package "
                                             + vaultPackage
                                             + ", please check META-INF/vault/properties.xml");
 
-                String version = packageProperties.getProperty(PackageProperties.NAME_VERSION);
+                String version = packageProperties.getVersionString();
                 if (version == null || version.isEmpty()) {
                     version = DEFEAULT_VERSION;
                 }
 
-                String description = packageProperties.getDescription();
-
-                featuresManager.init(group,
-                                     name,
-                                     version,
-                                     description);
+                featuresManager.init(group, name, version);
 
                 logger.info("Converting content-package '{}'...", vaultPackage.getId());
 
@@ -198,36 +227,6 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
         }
     }
 
-    protected Collection<VaultPackage> firstPass(File...contentPackages) throws Exception {
-        Map<PackageId, VaultPackage> idFileMap = new LinkedHashMap<>();
-        Map<PackageId, VaultPackage> idPackageMapping = new HashMap<>();
-
-        for (File contentPackage : contentPackages) {
-            requireNonNull(contentPackage, "Null content-package can not be converted.");
-
-            if (!contentPackage.exists() || !contentPackage.isFile()) {
-                throw new IllegalArgumentException("File " + contentPackage + " does not exist or it is a directory");
-            }
-
-            logger.info("Reading content-package '{}'...", contentPackage);
-
-            VaultPackage pack = packageManager.open(contentPackage, isStrictValidation());
-            idPackageMapping.put(pack.getId(), pack);
-
-            logger.info("content-package '{}' successfully read!", contentPackage);
-        }
-
-        logger.info("Ordering input content-package(s) {}...", idFileMap.keySet());
-
-        for (VaultPackage pack : new HashSet<VaultPackage>(idPackageMapping.values())) {
-            orderDependencies(idFileMap, idPackageMapping, pack, new HashSet<PackageId>());
-        }
-
-        logger.info("New content-package(s) order: {}", idFileMap.keySet());
-
-        return idFileMap.values();
-    }
-
     private void orderDependencies(Map<PackageId, VaultPackage> idFileMap,
                                    Map<PackageId, VaultPackage> idPackageMapping,
                                    VaultPackage pack,
@@ -249,21 +248,28 @@ public class ContentPackage2FeatureModelConverter extends BaseVaultPackageScanne
         idPackageMapping.remove(pack.getId());
     }
 
-    public void processSubPackage(String path, File contentPackage) throws Exception {
+    public void processSubPackage(String path, VaultPackage vaultPackage) throws Exception {
         requireNonNull(path, "Impossible to process a null vault package");
-        requireNonNull(contentPackage, "Impossible to process a null vault package");
+        requireNonNull(vaultPackage, "Impossible to process a null vault package");
 
-        try (VaultPackage vaultPackage = packageManager.open(contentPackage, isStrictValidation())) {
-            // scan the detected package, first
-            traverse(vaultPackage);
+        if (!isSubContentPackageIncluded(path)) {
+            logger.info("Sub content-package {} is filtered out, so it won't be processed.", path);
+            return;
+        }
 
-            // merge filters to the main new package
-            mainPackageAssembler.mergeFilters(vaultPackage.getMetaInf().getFilter());
+        // scan the detected package, first
+        traverse(vaultPackage);
 
-            // add the metadata-only package one to the main package
-            File clonedPackage = VaultPackageAssembler.create(vaultPackage).createPackage();
-            mainPackageAssembler.addEntry(path, clonedPackage);
-        }
+        // merge filters to the main new package
+        mainPackageAssembler.mergeFilters(vaultPackage.getMetaInf().getFilter());
+
+        // add the metadata-only package one to the main package
+        File clonedPackage = VaultPackageAssembler.create(vaultPackage).createPackage();
+        mainPackageAssembler.addEntry(path, clonedPackage);
+    }
+
+    protected boolean isSubContentPackageIncluded(String path) {
+        return subContentPackages.containsValue(path);
     }
 
     @Override
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 ff55d14..526cd9e 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
@@ -86,12 +86,8 @@ public class DefaultFeaturesManager implements FeaturesManager {
         this.properties = properties;
     }
 
-    public void init(String groupId,
-                     String artifactId,
-                     String version,
-                     String description) {
+    public void init(String groupId, String artifactId, String version) {
         targetFeature = new Feature(new ArtifactId(groupId, artifactId, version, null, SLING_OSGI_FEATURE_TILE_TYPE));
-        targetFeature.setDescription(description);
         runModes.clear();
     }
 
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/features/FeaturesManager.java b/src/main/java/org/apache/sling/feature/cpconverter/features/FeaturesManager.java
index 6c2f8b5..5c0b672 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/features/FeaturesManager.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/features/FeaturesManager.java
@@ -22,10 +22,7 @@ import org.apache.sling.feature.Feature;
 
 public interface FeaturesManager {
 
-    void init(String groupId,
-              String artifactId,
-              String version,
-              String description);
+    void init(String groupId, String artifactId, String version);
 
     Feature getTargetFeature();
 
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractContentPackageHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractContentPackageHandler.java
new file mode 100644
index 0000000..103a18f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/AbstractContentPackageHandler.java
@@ -0,0 +1,66 @@
+/*
+ * 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.cpconverter.handlers;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+
+public abstract class AbstractContentPackageHandler extends AbstractRegexEntryHandler {
+
+    private final File temporaryDir = new File(System.getProperty("java.io.tmpdir"), "sub-content-packages");
+
+    public AbstractContentPackageHandler() {
+        super("(?:jcr_root)?/etc/packages/.+\\.zip");
+        temporaryDir.mkdirs();
+    }
+
+    @Override
+    public final void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+        logger.info("Processing sub-content package '{}'...", entry.getName());
+
+        File temporaryContentPackage = new File(temporaryDir, entry.getName());
+
+        if (!temporaryContentPackage.exists()) {
+            logger.debug("Extracting sub-content package '{}' to {} for future analysis...", entry.getName(), temporaryContentPackage);
+
+            try (InputStream input = archive.openInputStream(entry);
+                    OutputStream output = new FileOutputStream(temporaryContentPackage)) {
+                IOUtils.copy(input, output);
+            }
+
+            logger.debug("Sub-content package '{}' successfully extracted to {} ", entry.getName(), temporaryContentPackage);
+        }
+
+        try (VaultPackage vaultPackage = converter.open(temporaryContentPackage)) {
+            processSubPackage(path, vaultPackage, converter);
+        }
+
+        logger.info("Sub-content package '{}' processing is over", entry.getName());
+    }
+
+    protected abstract void processSubPackage(String path, VaultPackage contentPackage, ContentPackage2FeatureModelConverter converter) throws Exception;
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java
index c849015..2fde1a0 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/ContentPackageEntryHandler.java
@@ -16,38 +16,15 @@
  */
 package org.apache.sling.feature.cpconverter.handlers;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.vault.fs.io.Archive;
-import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
 import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
 
-public final class ContentPackageEntryHandler extends AbstractRegexEntryHandler {
-
-    public ContentPackageEntryHandler() {
-        super("(?:jcr_root)?/etc/packages/.+\\.zip");
-    }
+public final class ContentPackageEntryHandler extends AbstractContentPackageHandler {
 
     @Override
-    public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter) throws Exception {
-        logger.info("Processing sub-content package '{}'...", entry.getName());
-
-        File temporaryContentPackage = File.createTempFile("content-package", entry.getName());
-
-        try (InputStream input = archive.openInputStream(entry);
-                OutputStream output = new FileOutputStream(temporaryContentPackage)) {
-            IOUtils.copy(input, output);
-        }
-
-        try {
-            converter.processSubPackage(path, temporaryContentPackage);
-        } finally {
-            temporaryContentPackage.delete();
-        }
+    protected void processSubPackage(String path, VaultPackage contentPackage, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+        converter.processSubPackage(path, contentPackage);
     }
 
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/VersionResolverContentPackageEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/VersionResolverContentPackageEntryHandler.java
new file mode 100644
index 0000000..2a57822
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/VersionResolverContentPackageEntryHandler.java
@@ -0,0 +1,82 @@
+/*
+ * 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.cpconverter.handlers;
+
+import java.util.Map;
+
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class VersionResolverContentPackageEntryHandler extends AbstractContentPackageHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Map<PackageId, String> subContentPackages;
+
+    public VersionResolverContentPackageEntryHandler(Map<PackageId, String> subContentPackages) {
+        this.subContentPackages = subContentPackages;
+    }
+
+    @Override
+    protected void processSubPackage(String path, VaultPackage contentPackage, ContentPackage2FeatureModelConverter converter)
+            throws Exception {
+
+        boolean addPackage = false;
+        PackageId currentId = contentPackage.getId();
+
+        logger.info("Checking if other {}:{} content-package versions were handled already", currentId.getGroup(), currentId.getName());
+
+        PackageId olderId = getPackage(currentId);
+
+        if (olderId != null) {
+            logger.info("Comparing {}:{} package versions: current one is {}, previous one is {} ",
+                        currentId.getGroup(), currentId.getName(), currentId.getVersionString(), olderId.getVersionString());
+
+            addPackage = currentId.compareTo(olderId) > 0;
+
+            if (addPackage) {
+                logger.info("Replacing version {} of content-package {}:{} with version {}",
+                            olderId.getVersionString(), currentId.getGroup(), currentId.getName(), currentId.getVersionString());
+
+                subContentPackages.remove(olderId);
+            }
+        } else {
+            logger.info("There were not other version of {}:{} content-package", currentId.getGroup(), currentId.getName());
+
+            addPackage = true;
+        }
+
+        if (addPackage) {
+            subContentPackages.put(currentId, path);
+        }
+    }
+
+    private PackageId getPackage(PackageId expectedId) {
+        for (PackageId currentId : subContentPackages.keySet()) {
+            if (expectedId.getGroup().equals(currentId.getGroup())
+                    && expectedId.getName().equals(currentId.getName())) {
+                return currentId;
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/BaseVaultPackageScanner.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/BaseVaultPackageScanner.java
index 3fb300d..5b67e06 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/BaseVaultPackageScanner.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/BaseVaultPackageScanner.java
@@ -14,29 +14,54 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package org.apache.sling.feature.cpconverter.vltpkg;
 
 import static java.util.Objects.requireNonNull;
 
+import java.io.File;
+
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.jackrabbit.vault.packaging.PackageManager;
 import org.apache.jackrabbit.vault.packaging.VaultPackage;
+import org.apache.jackrabbit.vault.packaging.impl.PackageManagerImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public abstract class BaseVaultPackageScanner {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    protected final PackageManager packageManager;
 
-    private final boolean strictValidation;
+    protected final boolean strictValidation;
 
     public BaseVaultPackageScanner(boolean strictValidation) {
+        this(new PackageManagerImpl(), strictValidation);
+    }
+
+    public BaseVaultPackageScanner(PackageManager packageManager, boolean strictValidation) {
+        this.packageManager = packageManager;
         this.strictValidation = strictValidation;
     }
 
-    public final boolean isStrictValidation() {
-        return strictValidation;
+    public VaultPackage open(File vaultPackage) throws Exception {
+        requireNonNull(vaultPackage, "Impossible to process a null vault package");
+        return packageManager.open(vaultPackage, strictValidation);
+    }
+
+    public final void traverse(File vaultPackageFile, boolean closeOnTraversed) throws Exception {
+        VaultPackage vaultPackage = null;
+        try {
+            vaultPackage = open(vaultPackageFile);
+            traverse(vaultPackage);
+        } finally {
+            if (closeOnTraversed) {
+                if (vaultPackage != null) {
+                    vaultPackage.close();
+                }
+            }
+        }
     }
 
     public final void traverse(VaultPackage vaultPackage) throws Exception {
@@ -66,11 +91,11 @@ public abstract class BaseVaultPackageScanner {
             return;
         }
 
-        logger.info("Processing entry {}...", entryPath);
+        logger.debug("Processing entry {}...", entryPath);
 
         onFile(entryPath, archive, entry);
 
-        logger.info("Entry {} successfully processed.", entryPath);
+        logger.debug("Entry {} successfully processed.", entryPath);
     }
 
     private static String newPath(String path, String entryName) {
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/RecollectorVaultPackageScanner.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/RecollectorVaultPackageScanner.java
new file mode 100644
index 0000000..1d0a747
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/RecollectorVaultPackageScanner.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cpconverter.vltpkg;
+
+import java.util.Map;
+
+import org.apache.jackrabbit.vault.fs.io.Archive;
+import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
+import org.apache.jackrabbit.vault.packaging.PackageId;
+import org.apache.jackrabbit.vault.packaging.PackageManager;
+import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.handlers.VersionResolverContentPackageEntryHandler;
+
+public final class RecollectorVaultPackageScanner extends BaseVaultPackageScanner {
+
+    private final ContentPackage2FeatureModelConverter converter;
+
+    private final VersionResolverContentPackageEntryHandler handler;
+
+    public RecollectorVaultPackageScanner(ContentPackage2FeatureModelConverter converter,
+                                          PackageManager packageManager,
+                                          boolean strictValidation,
+                                          Map<PackageId, String> subContentPackages) {
+        super(packageManager, strictValidation);
+        this.converter = converter;
+        handler = new VersionResolverContentPackageEntryHandler(subContentPackages);
+    }
+
+    @Override
+    protected void onFile(String path, Archive archive, Entry entry) throws Exception {
+        if (handler.matches(path)) {
+            handler.handle(path, archive, entry, converter);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
index fd3c754..366b8e0 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/vltpkg/VaultPackageAssembler.java
@@ -56,7 +56,7 @@ public class VaultPackageAssembler implements EntryHandler {
 
     private static final String[] INCLUDE_RESOURCES = { "definition/.content.xml", "config.xml", "settings.xml" };
 
-    private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir"));
+    private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir"), "syntethic-content-packages");
 
     private static final Pattern OSGI_BUNDLE_PATTERN = Pattern.compile("(jcr_root)?/apps/[^/]+/install(\\.([^/]+))?/.+\\.jar");
 
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 5ccb4ab..ea6e83e 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/ContentPackage2FeatureModelConverterTest.java
@@ -112,7 +112,7 @@ public class ContentPackage2FeatureModelConverterTest {
         URL packageUrl = getClass().getResource("test-content-package.zip");
         File packageFile = FileUtils.toFile(packageUrl);
 
-        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        File outputDirectory = new File(System.getProperty("java.io.tmpdir"), getClass().getName() + '_' + System.currentTimeMillis());
 
         converter.setFeaturesManager(new DefaultFeaturesManager(true, 5, outputDirectory, null, null))
                  .setBundlesDeployer(new DefaultArtifactsDeployer(outputDirectory))
@@ -237,7 +237,7 @@ public class ContentPackage2FeatureModelConverterTest {
         URL packageUrl = getClass().getResource("test-content-package-unacceptable.zip");
         File packageFile = FileUtils.toFile(packageUrl);
 
-        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        File outputDirectory = new File(System.getProperty("java.io.tmpdir"), getClass().getName() + '_' + System.currentTimeMillis());
 
         converter.setFeaturesManager(new DefaultFeaturesManager(true, 5, outputDirectory, null, null))
                  .setBundlesDeployer(new DefaultArtifactsDeployer(outputDirectory))
@@ -255,7 +255,7 @@ public class ContentPackage2FeatureModelConverterTest {
     }
 
     private void addSamePidConfiguration(String runmodeA, String runmodeB) throws Exception {
-        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        File outputDirectory = new File(System.getProperty("java.io.tmpdir"), getClass().getName() + '_' + System.currentTimeMillis());
         URL packageUrl = getClass().getResource("test-content-package.zip");
         File packageFile = FileUtils.toFile(packageUrl);
 
@@ -273,7 +273,7 @@ public class ContentPackage2FeatureModelConverterTest {
         URL packageUrl = getClass().getResource("test-content-package.zip");
         File packageFile = FileUtils.toFile(packageUrl);
 
-        File outputDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        File outputDirectory = new File(System.getProperty("java.io.tmpdir"), getClass().getName() + '_' + System.currentTimeMillis());
 
         String overrideId = "${project.groupId}:${project.artifactId}:slingosgifeature:asd.test.all-1.0.0:${project.version}";
         converter.setFeaturesManager(new DefaultFeaturesManager(true, 5, outputDirectory, overrideId, null))
@@ -337,6 +337,15 @@ public class ContentPackage2FeatureModelConverterTest {
         converter.firstPass(contentPackages);
     }
 
+    @Test
+    public void includeLatestUpdatedContentPackagesOnly() throws Exception {
+        File[] contentPackages = load("test-content-package.zip", "test-content-package-2.zip");
+        converter.firstPass(contentPackages);
+
+        assertTrue(converter.isSubContentPackageIncluded("jcr_root/etc/packages/asd/test-content-0.2.zip"));
+        assertFalse(converter.isSubContentPackageIncluded("jcr_root/etc/packages/asd/test-content.zip"));
+    }
+
     private File[] load(String...resources) {
         File[] loadedResources = new File[resources.length];
 
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
index 24a9e19..e336785 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/BundleEntryHandlerTest.java
@@ -95,7 +95,7 @@ public final class BundleEntryHandlerTest {
 
         ContentPackage2FeatureModelConverter converter = spy(ContentPackage2FeatureModelConverter.class);
 
-        File testDirectory = new File(System.getProperty("testDirectory"), getClass().getName() + '_' + System.currentTimeMillis());
+        File testDirectory = new File(System.getProperty("java.io.tmpdir"), getClass().getName() + '_' + System.currentTimeMillis());
         when(converter.getArtifactsDeployer()).thenReturn(new DefaultArtifactsDeployer(testDirectory));
         when(converter.getFeaturesManager()).thenReturn(featuresManager);
 
diff --git a/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package-2.zip b/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package-2.zip
new file mode 100644
index 0000000..87cfa94
Binary files /dev/null and b/src/test/resources/org/apache/sling/feature/cpconverter/test-content-package-2.zip differ