SLING-11809: Add option to overwrite primary node types of folders during extraction. (#117)

* SLING-11809: Add option to overwrite primary node types of folders during extraction.

* SLING-11809: Improve toString() in BookKeeperConfig and update related classes and tests.

---------

Co-authored-by: Danilo <dbanjac@adobe.com>
diff --git a/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfig.java b/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfig.java
index 8179e0e..2400670 100644
--- a/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfig.java
+++ b/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfig.java
@@ -18,6 +18,9 @@
  */
 package org.apache.sling.distribution.journal.bookkeeper;
 
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
 public class BookKeeperConfig {
     private final String subAgentName;
     private final String subSlingId;
@@ -25,19 +28,22 @@
     private final int maxRetries;
     private final PackageHandling packageHandling;
     private final String packageNodeName;
+    private final boolean extractorOverwriteFolderPrimaryTypes;
 
     public BookKeeperConfig(String subAgentName,
             String subSlingId,
             boolean editable, 
             int maxRetries,
             PackageHandling packageHandling, 
-            String packageNodeName) {
+            String packageNodeName,
+            boolean extractorOverwriteFolderPrimaryTypes) {
                 this.subAgentName = subAgentName;
                 this.subSlingId = subSlingId;
                 this.editable = editable;
                 this.maxRetries = maxRetries;
                 this.packageHandling = packageHandling;
                 this.packageNodeName = packageNodeName;
+                this.extractorOverwriteFolderPrimaryTypes = extractorOverwriteFolderPrimaryTypes;
     }
     
     public String getSubAgentName() {
@@ -63,10 +69,21 @@
     public String getPackageNodeName() {
         return packageNodeName;
     }
+
+    public boolean shouldExtractorOverwriteFolderPrimaryTypes() {
+        return extractorOverwriteFolderPrimaryTypes;
+    }
     
     @Override
     public String toString() {
-        return String.format("subAgentName=%S, subSlingId=%s, editable=%s, maxRetries=%s, packageHandling=%s, packageNodeName=%s",
-                subAgentName, subSlingId, editable, maxRetries, packageHandling, packageNodeName);
+        return new ToStringBuilder(this, ToStringStyle.JSON_STYLE)
+                .append("subAgentName", this.subAgentName)
+                .append("subSlingId", this.subSlingId)
+                .append("editable", this.editable)
+                .append("maxRetries", this.maxRetries)
+                .append("packageHandling", this.packageHandling)
+                .append("packageNodeName", this.packageNodeName)
+                .append("extractorOverwriteFolderPrimaryTypes", this.extractorOverwriteFolderPrimaryTypes)
+                .build();
     }
 }
diff --git a/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperFactory.java b/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperFactory.java
index b6a159b..b2df2ef 100644
--- a/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperFactory.java
+++ b/src/main/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperFactory.java
@@ -62,7 +62,10 @@
             Consumer<PackageStatusMessage> statusSender,
             Consumer<LogMessage> logSender
             ) {
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, config.getPackageHandling());
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging,
+                config.getPackageHandling(),
+                config.shouldExtractorOverwriteFolderPrimaryTypes());
         PackageHandler packageHandler = new PackageHandler(packageBuilder, extractor, binaryStore);
         return new BookKeeper(
                 resolverFactory, 
diff --git a/src/main/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractor.java b/src/main/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractor.java
index f020126..9c20bb7 100644
--- a/src/main/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractor.java
+++ b/src/main/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractor.java
@@ -54,10 +54,15 @@
 
     private final Packaging packageService;
     private final PackageHandling packageHandling;
+    private final boolean overwritePrimaryTypesOfFolders;
     
-    public ContentPackageExtractor(Packaging packageService, PackageHandling packageHandling) {
+    public ContentPackageExtractor(
+            Packaging packageService,
+            PackageHandling packageHandling,
+            boolean overwritePrimaryTypesOfFolders) {
         this.packageService = packageService;
         this.packageHandling = packageHandling;
+        this.overwritePrimaryTypesOfFolders = overwritePrimaryTypesOfFolders;
     }
     
     public void handle(ResourceResolver resourceResolver, List<String> paths) throws DistributionException {
@@ -114,7 +119,7 @@
     private void installPackage(JcrPackage pack, ErrorListener listener) throws RepositoryException, PackageException, IOException {
         ImportOptions opts = new ImportOptions();
         opts.setIdConflictPolicy(LEGACY);
-        opts.setOverwritePrimaryTypesOfFolders(false);
+        opts.setOverwritePrimaryTypesOfFolders(this.overwritePrimaryTypesOfFolders);
         opts.setListener(listener);
         opts.setStrict(true);
         if (packageHandling == PackageHandling.Extract) {
diff --git a/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/DistributionSubscriber.java b/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/DistributionSubscriber.java
index 6cb1797..ad2ebdd 100644
--- a/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/DistributionSubscriber.java
+++ b/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/DistributionSubscriber.java
@@ -193,8 +193,14 @@
         Consumer<LogMessage> logSender = messagingProvider.createSender(topics.getDiscoveryTopic());
 
         String packageNodeName = escapeTopicName(messagingProvider.getServerUri(), topics.getPackageTopic());
-        BookKeeperConfig bkConfig = new BookKeeperConfig(subAgentName, subSlingId, config.editable(),
-                config.maxRetries(), config.packageHandling(), packageNodeName);
+        BookKeeperConfig bkConfig = new BookKeeperConfig(
+                subAgentName,
+                subSlingId,
+                config.editable(),
+                config.maxRetries(),
+                config.packageHandling(),
+                packageNodeName,
+                config.contentPackageExtractorOverwritePrimaryTypesOfFolders());
         bookKeeper = bookKeeperFactory.create(packageBuilder, bkConfig, statusSender, logSender);
 
         long startOffset = bookKeeper.loadOffset() + 1;
diff --git a/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/SubscriberConfiguration.java b/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/SubscriberConfiguration.java
index a3ef9ce..fc2aa48 100644
--- a/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/SubscriberConfiguration.java
+++ b/src/main/java/org/apache/sling/distribution/journal/impl/subscriber/SubscriberConfiguration.java
@@ -55,4 +55,7 @@
     
     @AttributeDefinition(name = "subscriberIdleCheck", description = "Defines if we register a subscriber idle health check.")
     boolean subscriberIdleCheck() default false;
+
+    @AttributeDefinition(name = "ContentPackageExtractor.overwritePrimaryTypesOfFolders", description = "The flag determines whether the primary node types of folders should be overwritten during content package extraction, with a default value of 'true'.")
+    boolean contentPackageExtractorOverwritePrimaryTypesOfFolders() default true;
 }
diff --git a/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfigTest.java b/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfigTest.java
new file mode 100644
index 0000000..7631bee
--- /dev/null
+++ b/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperConfigTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.distribution.journal.bookkeeper;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class BookKeeperConfigTest {
+
+    @Test
+    public void testToString() {
+        BookKeeperConfig bookKeeperConfig = new BookKeeperConfig(
+                "subAgentName",
+                "subSlingId",
+                true,
+                10,
+                PackageHandling.Extract,
+                "package",
+                true);
+
+        assertEquals(
+                bookKeeperConfig.toString(),
+                "{\"subAgentName\":\"subAgentName\"," +
+                "\"subSlingId\":\"subSlingId\"," +
+                "\"editable\":true," +
+                "\"maxRetries\":10," +
+                "\"packageHandling\":\"Extract\"," +
+                "\"packageNodeName\":\"package\"," +
+                "\"extractorOverwriteFolderPrimaryTypes\":true}");
+    }
+}
diff --git a/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperTest.java b/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperTest.java
index 5ea7356..1ddb7b7 100644
--- a/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperTest.java
+++ b/src/test/java/org/apache/sling/distribution/journal/bookkeeper/BookKeeperTest.java
@@ -116,7 +116,7 @@
         when(distributionMetricsService.getPackageStatusCounter(any(String.class)))
                 .thenReturn(mock(Counter.class));
 
-        BookKeeperConfig bkConfig = new BookKeeperConfig("subAgentName", "subSlingId", true, 10, PackageHandling.Extract, "package");
+        BookKeeperConfig bkConfig = new BookKeeperConfig("subAgentName", "subSlingId", true, 10, PackageHandling.Extract, "package", true);
         bookKeeper = new BookKeeper(resolverFactory, distributionMetricsService, packageHandler, eventAdmin, sender, logSender, bkConfig,
             importPostProcessor, invalidationProcessor);
     }
diff --git a/src/test/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractorTest.java b/src/test/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractorTest.java
index fca2a40..a860f9a 100644
--- a/src/test/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractorTest.java
+++ b/src/test/java/org/apache/sling/distribution/journal/bookkeeper/ContentPackageExtractorTest.java
@@ -58,6 +58,7 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class ContentPackageExtractorTest {
+    private static final boolean OVERWRITE_PRIMARY_TYPES_OF_FOLDERS = true;
     @Rule
     public final SlingContext scontext = new SlingContext(ResourceResolverType.JCR_MOCK);
     
@@ -85,7 +86,8 @@
         Resource root = resourceResolver.getResource("/");
         Resource node = createNode(root, "other", NodeType.NT_FILE);
         
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Extract);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Extract, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
         
         verify(pkg, Mockito.never()).extract(Mockito.any(ImportOptions.class));
@@ -96,7 +98,8 @@
         Resource packages = createEtcPackages();
         Resource node = createNode(packages, "mypackage", NodeType.NT_UNSTRUCTURED);
         
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Extract); 
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Extract, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
         
         verify(pkg, Mockito.never()).extract(Mockito.any(ImportOptions.class));
@@ -107,7 +110,8 @@
     public void testNodeExtractNonExistantNode() throws Exception {
         Resource packages = createEtcPackages();
         
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Extract);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Extract, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         
         // Should log a warning but not any exception
         extractor.handle(resourceResolver, singletonList(packages.getPath() + "/invalid"));
@@ -117,7 +121,8 @@
     public void testOff() throws Exception {
         Resource node = createImportedPackage();
 
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Off); 
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Off, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
         
         verify(pkg, Mockito.never()).extract(Mockito.any(ImportOptions.class));
@@ -128,7 +133,8 @@
     public void testExtract() throws Exception {
         Resource node = createImportedPackage();
 
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Extract); 
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Extract, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
         
         verify(pkg).extract(Mockito.any(ImportOptions.class));
@@ -138,7 +144,8 @@
     public void testInstall() throws Exception {
         Resource node = createImportedPackage();
 
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install); 
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
         
         verify(pkg).install(Mockito.any(ImportOptions.class));
@@ -151,7 +158,8 @@
                 .install(Mockito.any(ImportOptions.class));
 
         Resource node = createImportedPackage();
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
     }
 
@@ -168,7 +176,8 @@
                 .install(any(ImportOptions.class));
 
         Resource node = createImportedPackage();
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
     }
 
@@ -178,7 +187,8 @@
         Resource packageRoot = getOrCreateResource(resourceResolver, "/tmp/packages", "package", "package", true);
         Resource node = createImportedPackage(packageRoot);
 
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
 
         verify(pkg, never()).install(Mockito.any(ImportOptions.class));
@@ -189,7 +199,8 @@
         Resource packageRoot = createEtcPackages();
         Resource node = createImportedPackage(packageRoot, NodeType.NT_FOLDER);
 
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(node.getPath()));
 
         verify(pkg, never()).install(Mockito.any(ImportOptions.class));
@@ -197,14 +208,16 @@
 
     @Test
     public void testNullPath() throws Exception {
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList(null));
         verify(pkg, never()).install(Mockito.any(ImportOptions.class));
     }
 
     @Test
     public void testNullPackageNode() throws Exception {
-        ContentPackageExtractor extractor = new ContentPackageExtractor(packaging, PackageHandling.Install);
+        ContentPackageExtractor extractor = new ContentPackageExtractor(
+                packaging, PackageHandling.Install, OVERWRITE_PRIMARY_TYPES_OF_FOLDERS);
         extractor.handle(resourceResolver, singletonList("/does/not/exist"));
         verify(pkg, never()).install(Mockito.any(ImportOptions.class));
     }