You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2025/05/09 08:25:11 UTC

(sling-org-apache-sling-feature-extension-content) branch master updated: SLING-12774 - Content feature launcher extension should reinstall SNAPSHOT content packages (#25)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f55bec7  SLING-12774 - Content feature launcher extension should reinstall SNAPSHOT content packages (#25)
f55bec7 is described below

commit f55bec716a2c9c39bf66f85da20cd239a12d9eff
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri May 9 10:25:06 2025 +0200

    SLING-12774 - Content feature launcher extension should reinstall SNAPSHOT content packages (#25)
    
    Enable reinstallation of snapshots only when the org.apache.sling.feature.extension.content.useStrictMode
    system property is set to true.
---
 README.md                                          |   6 +-
 .../feature/extension/content/ContentHandler.java  |  61 +++++--
 .../extension/content/ContentHandlerTest.java      | 176 +++++++++++++++++----
 3 files changed, 202 insertions(+), 41 deletions(-)

diff --git a/README.md b/README.md
index 8fea7fc..6a98008 100644
--- a/README.md
+++ b/README.md
@@ -20,4 +20,8 @@ The default start order is the maximum integer value (i.e. the package is proces
 
 All content-packages from the model are registered (externally) in a [`FSPackageRegistry`](https://jackrabbit.apache.org/filevault/apidocs/org/apache/jackrabbit/vault/packaging/registry/impl/FSPackageRegistry.html) and scheduled for installation with execution plans (given via OSGi configuration) which automatically get executed during repository start by [ExecutionPlanRepoInitializer](https://github.com/apache/sling-org-apache-sling-jcr-packageinit/blob/master/src/main/java/org/apache/s [...]
 
-Strict package mode installation can be enabled for all packages by setting the `org.apache.sling.feature.extension.content.useStrictMode` system property to true.
\ No newline at end of file
+Strict package mode installation can be enabled for all packages by setting the `org.apache.sling.feature.extension.content.useStrictMode` system property to true.
+
+## Reinstallation of SNAPSHOT content packages
+
+By default SNAPSHOT content packages are not reprocessed. If reinstallation is desired the `org.apache.sling.feature.extension.content.reinstallSnapshots` system property must be set to `true`. This requires at runtime a version 1.0.6 or newer of the `org.apache.sling.jcr.packageinit` bundle.
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/feature/extension/content/ContentHandler.java b/src/main/java/org/apache/sling/feature/extension/content/ContentHandler.java
index 3575272..64c6ecd 100644
--- a/src/main/java/org/apache/sling/feature/extension/content/ContentHandler.java
+++ b/src/main/java/org/apache/sling/feature/extension/content/ContentHandler.java
@@ -49,21 +49,26 @@ import org.apache.sling.feature.launcher.spi.extensions.ExtensionContext;
 import org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler;
 
 public class ContentHandler implements ExtensionHandler {
+    private static final String SYS_PROP_USE_STRICT_MODE = ContentHandler.class.getPackageName()+ ".useStrictMode";
+
+    private static final String SYS_PROP_REINSTALL_SNAPSHOTS = ContentHandler.class.getPackageName()+ ".reinstallSnapshots";
+
     public static final String PACKAGEREGISTRY_HOME = "packageregistry.home";
 
     private static final String REPOSITORY_HOME = "repository.home";
 
     private static final String REGISTRY_FOLDER = "packageregistry";
 
-    private static ExecutionPlanBuilder buildExecutionPlan(Collection<Artifact> artifacts, Set<PackageId> satisfiedPackages, LauncherPrepareContext prepareContext, File registryHome, boolean useStrictMode) throws Exception {
+    private static ExecutionPlanBuilderWithDetails buildExecutionPlan(Collection<Artifact> artifacts, Set<PackageId> satisfiedPackages, LauncherPrepareContext prepareContext, File registryHome, 
+            boolean useStrictMode, boolean reinstallSnapshots) throws Exception {
 
-        List<File> packageReferences = new ArrayList<>();
+        List<PackageReference> packageReferences = new ArrayList<>();
 
         for (final Artifact a : artifacts) {
             final URL file = prepareContext.getArtifactFile(a.getId());
             File tmp = IOUtils.getFileFromURL(file, true, null);
             if (tmp != null && tmp.length() > 0) {
-                packageReferences.add(tmp);
+                packageReferences.add(new PackageReference(tmp, a));
             }
         }
 
@@ -75,10 +80,11 @@ public class ContentHandler implements ExtensionHandler {
 
         ExecutionPlanBuilder builder = registry.createExecutionPlan();
         builder.with(satisfiedPackages);
-
-        for (File pkgFile : packageReferences) {
+        boolean hasAnySnapshot = false;
+        for (PackageReference pkgRef : packageReferences) {
+            hasAnySnapshot |= pkgRef.isSnapshot;
             try {
-                PackageId pid = registry.registerExternal(pkgFile, false);
+                PackageId pid = registry.registerExternal(pkgRef.file, pkgRef.isSnapshot && reinstallSnapshots);
                 ImportOptions importOptions = new ImportOptions();
                 importOptions.setStrict(useStrictMode);
                 PackageTaskOptions options = new ImportOptionsPackageTaskOption(importOptions);
@@ -89,8 +95,8 @@ public class ContentHandler implements ExtensionHandler {
         }
         builder.validate();
         satisfiedPackages.addAll(builder.preview());
-        return builder;
-
+        
+        return new ExecutionPlanBuilderWithDetails(builder, hasAnySnapshot);
     }
 
     @Override
@@ -99,7 +105,8 @@ public class ContentHandler implements ExtensionHandler {
         if (extension.getType() == ExtensionType.ARTIFACTS
                 && extension.getName().equals(Extension.EXTENSION_NAME_CONTENT_PACKAGES)) {
 
-        	boolean useStrictMode = Boolean.getBoolean(getClass().getPackageName()+ ".useStrictMode");
+        	boolean useStrictMode = Boolean.getBoolean(SYS_PROP_USE_STRICT_MODE);
+        	boolean reinstallSnapshots = Boolean.getBoolean(SYS_PROP_REINSTALL_SNAPSHOTS);
 
             Map<Integer, Collection<Artifact>> orderedArtifacts = new TreeMap<>();
             for (final Artifact a : extension.getArtifacts()) {
@@ -114,16 +121,28 @@ public class ContentHandler implements ExtensionHandler {
             }
             List<String> executionPlans = new ArrayList<>();
             Set<PackageId> satisfiedPackages = new HashSet<>();
+            boolean anyPlanIncludesSnapshots = false;
             for (Collection<Artifact> artifacts : orderedArtifacts.values()) {
-                ExecutionPlanBuilder builder = buildExecutionPlan(artifacts, satisfiedPackages, context, registryHome, useStrictMode);
+                ExecutionPlanBuilderWithDetails builderWithDetails = buildExecutionPlan(artifacts, satisfiedPackages, context, registryHome, useStrictMode, reinstallSnapshots);
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                builder.save(baos);
+                builderWithDetails.builder.save(baos);
                 executionPlans.add(baos.toString(StandardCharsets.UTF_8));
+                anyPlanIncludesSnapshots |= builderWithDetails.includesSnapshots;
             }
+            
+            if ( anyPlanIncludesSnapshots ) {
+                if ( reinstallSnapshots ) {
+                    context.getLogger().info("Found at least one SNAPSHOT package - configuring ExecutionPlanRepoInitializer to reprocess all plans that contain SNAPSHOT packages.");
+                } else {
+                    context.getLogger().info("Found at least one SNAPSHOT package but ExecutionPlanRepoInitializer is not configured by default to reinstall SNAPSHOTS. Set system property {} to true to enable.", SYS_PROP_REINSTALL_SNAPSHOTS);
+                }
+            }
+            
             // Workaround for too bold relocation mechanism - corresponding details at https://issues.apache.org/jira/browse/MSHADE-156
             final Configuration initcfg = new Configuration("org.apache.sling.jcr.packageinit.impl.ExecutionPlanRepoInitializer");
             initcfg.getProperties().put("executionplans", executionPlans.toArray(new String[executionPlans.size()]));
             initcfg.getProperties().put("statusfilepath", registryHome.getAbsolutePath() + File.separator + "executedplans.file");
+            initcfg.getProperties().put("reinstallSnapshots", anyPlanIncludesSnapshots && reinstallSnapshots);
             context.addConfiguration(initcfg.getPid(), null, initcfg.getProperties());
             // Workaround for too bold relocation mechanism - corresponding details at https://issues.apache.org/jira/browse/MSHADE-156
             final Configuration registrycfg = new Configuration("org.UNSHADE.apache.jackrabbit.vault.packaging.registry.impl.FSPackageRegistry");
@@ -158,4 +177,24 @@ public class ContentHandler implements ExtensionHandler {
         }
         return registryHome;
     }
+    
+    static class PackageReference {
+        private File file;
+        private boolean isSnapshot;
+        
+        public PackageReference(File file, Artifact artifact) {
+            this.file = file;
+            this.isSnapshot = artifact.getId().getVersion().endsWith("-SNAPSHOT");
+        }
+    }
+    
+    static class ExecutionPlanBuilderWithDetails {
+        private ExecutionPlanBuilder builder;
+        private boolean includesSnapshots;
+        
+        public ExecutionPlanBuilderWithDetails(ExecutionPlanBuilder builder, boolean includesSnapshots) {
+            this.builder = builder;
+            this.includesSnapshots = includesSnapshots;
+        }
+    }
 }
diff --git a/src/test/java/org/apache/sling/feature/extension/content/ContentHandlerTest.java b/src/test/java/org/apache/sling/feature/extension/content/ContentHandlerTest.java
index 589f90d..f3f23ed 100644
--- a/src/test/java/org/apache/sling/feature/extension/content/ContentHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/content/ContentHandlerTest.java
@@ -17,7 +17,6 @@
 package org.apache.sling.feature.extension.content;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
@@ -28,6 +27,9 @@ import java.net.URL;
 import java.util.Dictionary;
 import java.util.Iterator;
 
+import org.apache.jackrabbit.vault.packaging.registry.impl.AbstractPackageRegistry.SecurityConfig;
+import org.apache.jackrabbit.vault.packaging.registry.impl.FSPackageRegistry;
+import org.apache.jackrabbit.vault.packaging.registry.impl.InstallationScope;
 import org.apache.sling.feature.Artifact;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Extension;
@@ -42,6 +44,7 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.slf4j.LoggerFactory;
 
 @RunWith(MockitoJUnitRunner.class)
 public class ContentHandlerTest {
@@ -73,43 +76,48 @@ public class ContentHandlerTest {
     private static final String COORDINATES_TEST_PACKAGE_C_10 = "my_packages:test_c:1.0";
     private static String TEST_PACKAGE_C_10 = "testpackages/test_c-1.0.zip";
     private static ArtifactId TEST_PACKAGE_AID_C_10 = ArtifactId.fromMvnId(COORDINATES_TEST_PACKAGE_C_10);
+    private static ArtifactId TEST_PACKAGE_AID_C_10_SNAPSHOT = TEST_PACKAGE_AID_C_10.changeVersion("1.0-SNAPSHOT");
 
     @Before
     public void setUp() throws Exception {
-        URL test_a = this.getClass().getResource(TEST_PACKAGE_A_10);
-        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_A_10)).thenReturn(test_a);
-        URL test_b = this.getClass().getResource(TEST_PACKAGE_B_10);
-        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_B_10)).thenReturn(test_b);
-        URL test_c = this.getClass().getResource(TEST_PACKAGE_C_10);
-        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_C_10)).thenReturn(test_c);
+        URL testA = this.getClass().getResource(TEST_PACKAGE_A_10);
+        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_A_10)).thenReturn(testA);
+        URL testB = this.getClass().getResource(TEST_PACKAGE_B_10);
+        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_B_10)).thenReturn(testB);
+        URL testC = this.getClass().getResource(TEST_PACKAGE_C_10);
+        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_C_10)).thenReturn(testC);
+        when(extensionContext.getArtifactFile(TEST_PACKAGE_AID_C_10_SNAPSHOT)).thenReturn(testC);
+        
+        when(extensionContext.getLogger()).thenReturn(LoggerFactory.getLogger(getClass().getName()));
+        
+        System.setProperty(ContentHandler.PACKAGEREGISTRY_HOME, testFolder.getRoot().toString());
+        
+        System.clearProperty(ContentHandler.class.getPackageName()+".reinstallSnapshots");
     }
 
+    /**
+     * Validates that the content handler creates the execution plan correctly
+     * 
+     * @throws Exception
+     */
     @Test
     public void testMultipleStartOrders() throws Exception {
         ContentHandler ch = new ContentHandler();
-        System.setProperty(ContentHandler.PACKAGEREGISTRY_HOME, testFolder.getRoot().toString());
+        
         Extension ext = new Extension(ExtensionType.ARTIFACTS, "content-packages", ExtensionState.OPTIONAL);
-        Artifact artifact_a = new Artifact(TEST_PACKAGE_AID_A_10);
-        Artifact artifact_b = new Artifact(TEST_PACKAGE_AID_B_10);
-        Artifact artifact_c = new Artifact(TEST_PACKAGE_AID_C_10);
-        artifact_a.getMetadata().put("start-order", "2");
-        artifact_b.getMetadata().put("start-order", "1");
-        artifact_c.getMetadata().put("start-order", "1");
-        ext.getArtifacts().add(artifact_a);
-        ext.getArtifacts().add(artifact_b);
-        ext.getArtifacts().add(artifact_c);
-        @SuppressWarnings("unchecked")
-        ArgumentCaptor<Dictionary<String, Object>> executionPlanCaptor = ArgumentCaptor.forClass(Dictionary.class);
+        
+        Artifact artifactA = new Artifact(TEST_PACKAGE_AID_A_10);
+        Artifact artifactB = new Artifact(TEST_PACKAGE_AID_B_10);
+        Artifact artifactC = new Artifact(TEST_PACKAGE_AID_C_10);
+        artifactA.getMetadata().put("start-order", "2");
+        artifactB.getMetadata().put("start-order", "1");
+        artifactC.getMetadata().put("start-order", "1");
+        ext.getArtifacts().add(artifactA);
+        ext.getArtifacts().add(artifactB);
+        ext.getArtifacts().add(artifactC);
+        
+        final String[] executionplans = execute(ch, ext, false);
 
-        ch.handle(extensionContext, ext);
-        verify(extensionContext).addConfiguration(eq("org.apache.sling.jcr.packageinit.impl.ExecutionPlanRepoInitializer"), any(), executionPlanCaptor.capture());
-        verify(extensionContext).addConfiguration(eq("org.UNSHADE.apache.jackrabbit.vault.packaging.registry.impl.FSPackageRegistry"), any(), any());
-        Iterator<Dictionary<String, Object>> dictIt = executionPlanCaptor.getAllValues().iterator();
-        Dictionary<String, Object> dict = dictIt.next();
-        final String[] executionplans = (String[]) dict.get("executionplans");
-        final String statusFileHome = (String)dict.get("statusfilepath");
-        File executedPlansFile = new File(testFolder.getRoot(), "executedplans.file");
-        assertEquals(executedPlansFile.getAbsolutePath(), statusFileHome);
         final String expected_0 =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                         "<executionPlan version=\"1.0\">\n" +
@@ -146,6 +154,116 @@ public class ContentHandlerTest {
                         "</executionPlan>\n";
 
         assertEquals(expected_1, executionplans[1]);
-        assertFalse(dictIt.hasNext());
+        assertEquals(2, executionplans.length);
+    }
+    
+    /**
+     * Validates that a package that is already registered is skipped from the execution plan.
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void duplicatePackage() throws Exception {
+        
+        FSPackageRegistry registry = new FSPackageRegistry(testFolder.getRoot(), InstallationScope.UNSCOPED, new SecurityConfig(null, null), true);
+        registry.register(getClass().getResourceAsStream(TEST_PACKAGE_C_10), false);
+        
+        ContentHandler ch = new ContentHandler();
+        Extension ext = new Extension(ExtensionType.ARTIFACTS, "content-packages", ExtensionState.OPTIONAL);
+        Artifact artifactC = new Artifact(TEST_PACKAGE_AID_C_10);
+        artifactC.getMetadata().put("start-order", "1");
+        ext.getArtifacts().add(artifactC);
+        final String[] executionplans = execute(ch, ext, false);
+
+        final String expected_0 =
+                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                        "<executionPlan version=\"1.0\"/>\n";
+
+        assertEquals(expected_0, executionplans[0]);
+        assertEquals(1, executionplans.length);
+        
+    }
+    
+    /**
+     * Validates that a SNAPSHOT package that is already registered is included in the execution plan when the corresponding system property is set.
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void duplicateSnapshotPackage_reinstall() throws Exception {
+        
+        System.setProperty(ContentHandler.class.getPackageName()+".reinstallSnapshots", "true");
+        
+        FSPackageRegistry registry = new FSPackageRegistry(testFolder.getRoot(), InstallationScope.UNSCOPED, new SecurityConfig(null, null), true);
+        registry.register(getClass().getResourceAsStream(TEST_PACKAGE_C_10), false);
+        
+        ContentHandler ch = new ContentHandler();
+        Extension ext = new Extension(ExtensionType.ARTIFACTS, "content-packages", ExtensionState.OPTIONAL);
+        Artifact artifactC = new Artifact(TEST_PACKAGE_AID_C_10_SNAPSHOT);
+        artifactC.getMetadata().put("start-order", "1");
+        ext.getArtifacts().add(artifactC);
+        
+        final String[] executionplans = execute(ch, ext, true);
+
+        final String expected_0 =
+                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                        "<executionPlan version=\"1.0\">\n" +
+                        "    <task cmd=\"extract\" packageId=\"my_packages:test_c:1.0\">\n" +
+                        "        <options type=\"ImportOptions\">\n" +
+                        "            <isStrict>false</isStrict>\n" +
+                        "            <autoSaveThreshold>-1</autoSaveThreshold>\n" +
+                        "            <nonRecursive>false</nonRecursive>\n" +
+                        "            <dryRun>false</dryRun>\n" +
+                        "        </options>\n" +
+                        "    </task>\n" +
+                        "</executionPlan>\n";
+
+        assertEquals(expected_0, executionplans[0]);
+        assertEquals(1, executionplans.length);
+    }
+
+    /**
+     * Validates that a SNAPSHOT package that is already registered is included in the execution plan when the corresponding system property is not set.
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void duplicateSnapshotPackage_skip() throws Exception {
+        
+        FSPackageRegistry registry = new FSPackageRegistry(testFolder.getRoot(), InstallationScope.UNSCOPED, new SecurityConfig(null, null), true);
+        registry.register(getClass().getResourceAsStream(TEST_PACKAGE_C_10), false);
+        
+        ContentHandler ch = new ContentHandler();
+        Extension ext = new Extension(ExtensionType.ARTIFACTS, "content-packages", ExtensionState.OPTIONAL);
+        Artifact artifactC = new Artifact(TEST_PACKAGE_AID_C_10_SNAPSHOT);
+        artifactC.getMetadata().put("start-order", "1");
+        ext.getArtifacts().add(artifactC);
+        
+        final String[] executionplans = execute(ch, ext, false);
+
+        final String expected_0 =
+                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                        "<executionPlan version=\"1.0\"/>\n";
+
+        assertEquals(expected_0, executionplans[0]);
+        assertEquals(1, executionplans.length);
+    }
+
+    private String[] execute(ContentHandler ch, Extension ext, boolean expectedReinstallSnapshots) throws Exception {
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<Dictionary<String, Object>> executionPlanCaptor = ArgumentCaptor.forClass(Dictionary.class);
+
+        ch.handle(extensionContext, ext);
+        verify(extensionContext).addConfiguration(eq("org.apache.sling.jcr.packageinit.impl.ExecutionPlanRepoInitializer"), any(), executionPlanCaptor.capture());
+        verify(extensionContext).addConfiguration(eq("org.UNSHADE.apache.jackrabbit.vault.packaging.registry.impl.FSPackageRegistry"), any(), any());
+        Iterator<Dictionary<String, Object>> dictIt = executionPlanCaptor.getAllValues().iterator();
+        Dictionary<String, Object> dict = dictIt.next();
+        final String[] executionplans = (String[]) dict.get("executionplans");
+        final String statusFileHome = (String)dict.get("statusfilepath");
+        final Boolean reinstallSnapshots = (Boolean)dict.get("reinstallSnapshots");
+        assertEquals("reinstallSnapshots configuration property", expectedReinstallSnapshots, reinstallSnapshots);
+        File executedPlansFile = new File(testFolder.getRoot(), "executedplans.file");
+        assertEquals(executedPlansFile.getAbsolutePath(), statusFileHome);
+        return executionplans;
     }
 }