You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by el...@apache.org on 2026/08/05 11:23:12 UTC

(maven-assembly-plugin) branch issue-1349-aggregating-handler created (now 72497748)

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

elharo pushed a change to branch issue-1349-aggregating-handler
in repository https://gitbox.apache.org/repos/asf/maven-assembly-plugin.git


      at 72497748 Fix #1349: complete the aggregation in the container descriptor handlers

This branch includes the following new commits:

     new 72497748 Fix #1349: complete the aggregation in the container descriptor handlers

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(maven-assembly-plugin) 01/01: Fix #1349: complete the aggregation in the container descriptor handlers

Posted by el...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

elharo pushed a commit to branch issue-1349-aggregating-handler
in repository https://gitbox.apache.org/repos/asf/maven-assembly-plugin.git

commit 724977485df159e0176a73ca5acd903b181783ac
Author: Elliotte Rusty Harold <el...@ibiblio.org>
AuthorDate: Wed Aug 5 11:23:01 2026 +0000

    Fix #1349: complete the aggregation in the container descriptor handlers
    
    The FIXME comments in AbstractLineAggregatingHandler and
    ComponentsXmlArchiverFileFilter assumed the resource scan could be moved
    into plexus-archiver's AbstractArchiver, but the scan is required: the
    archiver runs its finalizers before scanning the added resources, so the
    file selectors are only applied while iterating. Document the actual
    behavior and add the missing resource scan to
    SimpleAggregatingDescriptorHandler, whose aggregated file was previously
    empty.
---
 .../filter/AbstractLineAggregatingHandler.java     |  10 +-
 .../filter/ComponentsXmlArchiverFileFilter.java    |  10 +-
 .../filter/SimpleAggregatingDescriptorHandler.java |  10 ++
 .../ComponentsXmlArchiverFileFilterTest.java       |  77 +++++++++++++
 .../SimpleAggregatingDescriptorHandlerTest.java    | 123 +++++++++++++++++++++
 5 files changed, 220 insertions(+), 10 deletions(-)

diff --git a/src/main/java/org/apache/maven/plugins/assembly/filter/AbstractLineAggregatingHandler.java b/src/main/java/org/apache/maven/plugins/assembly/filter/AbstractLineAggregatingHandler.java
index e1bb8122..4c72e8ac 100644
--- a/src/main/java/org/apache/maven/plugins/assembly/filter/AbstractLineAggregatingHandler.java
+++ b/src/main/java/org/apache/maven/plugins/assembly/filter/AbstractLineAggregatingHandler.java
@@ -65,11 +65,11 @@ abstract class AbstractLineAggregatingHandler implements ContainerDescriptorHand
 
     @Override
     public void finalizeArchiveCreation(final Archiver archiver) {
-        // this will prompt the isSelected() call, below, for all resources added to the archive.
-        // FIXME: This needs to be corrected in the AbstractArchiver, where
-        // runArchiveFinalizers() is called before regular resources are added...
-        // which is done because the manifest needs to be added first, and the
-        // manifest-creation component is a finalizer in the assembly plugin...
+        // The archiver runs its finalizers before it scans the resources that have
+        // been added so far (see AbstractArchiver.createArchive()), because the
+        // manifest has to be added first. Iterating the added resources up front
+        // applies the file selectors of every added resource collection, which
+        // invokes this handler's isSelected() and collects the content to aggregate.
         for (final ResourceIterator it = archiver.getResources(); it.hasNext(); ) {
             it.next();
         }
diff --git a/src/main/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilter.java b/src/main/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilter.java
index e835452e..c5b14ea7 100644
--- a/src/main/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilter.java
+++ b/src/main/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilter.java
@@ -116,11 +116,11 @@ public class ComponentsXmlArchiverFileFilter implements ContainerDescriptorHandl
 
     @Override
     public void finalizeArchiveCreation(final Archiver archiver) {
-        // this will prompt the isSelected() call, below, for all resources added to the archive.
-        // FIXME: This needs to be corrected in the AbstractArchiver, where
-        // runArchiveFinalizers() is called before regular resources are added...
-        // which is done because the manifest needs to be added first, and the
-        // manifest-creation component is a finalizer in the assembly plugin...
+        // The archiver runs its finalizers before it scans the resources that have
+        // been added so far (see AbstractArchiver.createArchive()), because the
+        // manifest has to be added first. Iterating the added resources up front
+        // applies the file selectors of every added resource collection, which
+        // invokes this handler's isSelected() and collects the content to aggregate.
         for (final ResourceIterator it = archiver.getResources(); it.hasNext(); ) {
             it.next();
         }
diff --git a/src/main/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandler.java b/src/main/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandler.java
index af30cf56..c5570ec0 100644
--- a/src/main/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandler.java
+++ b/src/main/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandler.java
@@ -38,6 +38,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.maven.plugins.assembly.utils.AssemblyFileUtils;
 import org.codehaus.plexus.archiver.Archiver;
 import org.codehaus.plexus.archiver.ArchiverException;
+import org.codehaus.plexus.archiver.ResourceIterator;
 import org.codehaus.plexus.archiver.UnArchiver;
 import org.codehaus.plexus.components.io.fileselectors.FileInfo;
 
@@ -67,6 +68,15 @@ public class SimpleAggregatingDescriptorHandler implements ContainerDescriptorHa
     public void finalizeArchiveCreation(final Archiver archiver) {
         checkConfig();
 
+        // The archiver runs its finalizers before it scans the resources that have
+        // been added so far (see AbstractArchiver.createArchive()), because the
+        // manifest has to be added first. Iterating the added resources up front
+        // applies the file selectors of every added resource collection, which
+        // invokes this handler's isSelected() and collects the content to aggregate.
+        for (final ResourceIterator it = archiver.getResources(); it.hasNext(); ) {
+            it.next();
+        }
+
         if (outputPath.endsWith("/")) {
             throw new ArchiverException("Cannot write aggregated properties to a directory. "
                     + "You must specify a file name in the outputPath configuration for this"
diff --git a/src/test/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilterTest.java b/src/test/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilterTest.java
index c35afbc4..fd748c59 100644
--- a/src/test/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilterTest.java
+++ b/src/test/java/org/apache/maven/plugins/assembly/filter/ComponentsXmlArchiverFileFilterTest.java
@@ -23,7 +23,9 @@ import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -41,6 +43,8 @@ import org.codehaus.plexus.archiver.FileSet;
 import org.codehaus.plexus.archiver.ResourceIterator;
 import org.codehaus.plexus.archiver.diags.NoOpArchiver;
 import org.codehaus.plexus.archiver.zip.ZipArchiver;
+import org.codehaus.plexus.components.io.fileselectors.FileSelector;
+import org.codehaus.plexus.components.io.resources.DefaultPlexusIoFileResourceCollection;
 import org.codehaus.plexus.components.io.resources.PlexusIoResource;
 import org.codehaus.plexus.components.io.resources.PlexusIoResourceCollection;
 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
@@ -60,6 +64,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class ComponentsXmlArchiverFileFilterTest {
 
@@ -292,6 +297,78 @@ class ComponentsXmlArchiverFileFilterTest {
         assertEquals("impl", implementation2.evaluateFirst(doc).getText());
     }
 
+    @Test
+    void shouldAggregateComponentsFromResourcesAddedToTheArchive() throws Exception {
+        final Path dirA = Files.createDirectories(temporaryFolder.toPath().resolve("a"));
+        final Path dirB = Files.createDirectories(temporaryFolder.toPath().resolve("b"));
+
+        writeComponentDescriptor(dirA, "role1", "impl1");
+        writeComponentDescriptor(dirB, "role2", "impl2");
+
+        final ZipArchiver archiver = new ZipArchiver();
+
+        final File archiveFile = newFile(temporaryFolder, "archive");
+
+        archiver.setDestFile(archiveFile);
+
+        archiver.addResources(componentResourceCollection(dirA.toFile(), filter));
+        archiver.addResources(componentResourceCollection(dirB.toFile(), filter));
+
+        archiver.setArchiveFinalizers(Collections.singletonList(filter));
+
+        archiver.createArchive();
+
+        try (ZipFile zf = new ZipFile(archiveFile)) {
+            final ZipEntry ze = zf.getEntry(ComponentsXmlArchiverFileFilter.COMPONENTS_XML_PATH);
+
+            assertNotNull(ze);
+
+            final String content = new String(Files.readAllBytes(copyToFile(zf, ze)), StandardCharsets.UTF_8);
+
+            assertTrue(content.contains("<role>role1</role>"), "aggregated components.xml should contain role1");
+            assertTrue(content.contains("<role>role2</role>"), "aggregated components.xml should contain role2");
+            assertTrue(
+                    content.contains("<implementation>impl1</implementation>"),
+                    "aggregated components.xml should contain impl1");
+            assertTrue(
+                    content.contains("<implementation>impl2</implementation>"),
+                    "aggregated components.xml should contain impl2");
+        }
+    }
+
+    private void writeComponentDescriptor(final Path baseDir, final String role, final String implementation)
+            throws IOException {
+        Files.createDirectories(baseDir.resolve("META-INF/plexus"));
+
+        final Path descriptor = baseDir.resolve(ComponentsXmlArchiverFileFilter.COMPONENTS_XML_PATH);
+
+        Files.write(
+                descriptor,
+                ("<component-set><components><component><role>" + role
+                                + "</role><implementation>" + implementation
+                                + "</implementation></component></components></component-set>")
+                        .getBytes(StandardCharsets.UTF_8));
+    }
+
+    private DefaultPlexusIoFileResourceCollection componentResourceCollection(
+            final File baseDir, final FileSelector selector) {
+        final DefaultPlexusIoFileResourceCollection collection = new DefaultPlexusIoFileResourceCollection();
+
+        collection.setBaseDir(baseDir);
+        collection.setIncludes(new String[] {"**/components.xml"});
+        collection.setFileSelectors(new FileSelector[] {selector});
+
+        return collection;
+    }
+
+    private Path copyToFile(final ZipFile zf, final ZipEntry ze) throws IOException {
+        final Path dest = new File(temporaryFolder, "descriptor.xml").toPath();
+
+        Files.copy(zf.getInputStream(ze), dest);
+
+        return dest;
+    }
+
     private Xpp3Dom createComponentDom(final ComponentDef def) {
         final Xpp3Dom dom = new Xpp3Dom("component");
 
diff --git a/src/test/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandlerTest.java b/src/test/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandlerTest.java
new file mode 100644
index 00000000..4b28ff20
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/assembly/filter/SimpleAggregatingDescriptorHandlerTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.maven.plugins.assembly.filter;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.codehaus.plexus.archiver.zip.ZipArchiver;
+import org.codehaus.plexus.components.io.fileselectors.FileSelector;
+import org.codehaus.plexus.components.io.resources.DefaultPlexusIoFileResourceCollection;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SimpleAggregatingDescriptorHandlerTest {
+
+    @TempDir
+    private Path temporaryFolder;
+
+    @Test
+    void shouldAggregateMatchingFilesIntoOutputPath() throws Exception {
+        final SimpleAggregatingDescriptorHandler handler = new SimpleAggregatingDescriptorHandler();
+        handler.setFilePattern(".*/file\\.txt");
+        handler.setOutputPath("file.txt");
+
+        final Path baseDir = Files.createDirectories(temporaryFolder.resolve("src"));
+        Files.createDirectories(baseDir.resolve("a"));
+        Files.createDirectories(baseDir.resolve("b"));
+        Files.write(baseDir.resolve("a/file.txt"), "file A\n".getBytes(StandardCharsets.UTF_8));
+        Files.write(baseDir.resolve("b/file.txt"), "file B\n".getBytes(StandardCharsets.UTF_8));
+
+        final ZipArchiver archiver = new ZipArchiver();
+        final File archiveFile = new File(temporaryFolder.toFile(), "archive.zip");
+        archiver.setDestFile(archiveFile);
+        archiver.addResources(resourceCollection(baseDir.toFile(), handler));
+        archiver.setArchiveFinalizers(Collections.singletonList(handler));
+
+        archiver.createArchive();
+
+        try (ZipFile zf = new ZipFile(archiveFile)) {
+            final ZipEntry entry = zf.getEntry("file.txt");
+            assertTrue(entry != null, "aggregated file.txt should be present in the archive");
+
+            final String content = new String(readAllBytes(zf.getInputStream(entry)), StandardCharsets.UTF_8);
+
+            assertTrue(content.contains("file A"), "aggregated content should contain file A");
+            assertTrue(content.contains("file B"), "aggregated content should contain file B");
+        }
+    }
+
+    @Test
+    void shouldNotIncludeAggregatedSourcesInArchive() throws Exception {
+        final SimpleAggregatingDescriptorHandler handler = new SimpleAggregatingDescriptorHandler();
+        handler.setFilePattern(".*/file\\.txt");
+        handler.setOutputPath("file.txt");
+
+        final Path baseDir = Files.createDirectories(temporaryFolder.resolve("src"));
+        Files.createDirectories(baseDir.resolve("a"));
+        Files.createDirectories(baseDir.resolve("b"));
+        Files.write(baseDir.resolve("a/file.txt"), "file A\n".getBytes(StandardCharsets.UTF_8));
+        Files.write(baseDir.resolve("b/file.txt"), "file B\n".getBytes(StandardCharsets.UTF_8));
+
+        final ZipArchiver archiver = new ZipArchiver();
+        final File archiveFile = new File(temporaryFolder.toFile(), "archive.zip");
+        archiver.setDestFile(archiveFile);
+        archiver.addResources(resourceCollection(baseDir.toFile(), handler));
+        archiver.setArchiveFinalizers(Collections.singletonList(handler));
+
+        archiver.createArchive();
+
+        try (ZipFile zf = new ZipFile(archiveFile)) {
+            final List<? extends ZipEntry> entries = Collections.list(zf.entries());
+
+            assertEquals(
+                    1,
+                    entries.stream().filter(e -> !e.isDirectory()).count(),
+                    "the matching sources should be excluded, only the aggregated file.txt should remain");
+        }
+    }
+
+    private DefaultPlexusIoFileResourceCollection resourceCollection(final File baseDir, final FileSelector selector)
+            throws IOException {
+        final DefaultPlexusIoFileResourceCollection collection = new DefaultPlexusIoFileResourceCollection();
+        collection.setBaseDir(baseDir);
+        collection.setIncludes(new String[] {"**/file.txt"});
+        collection.setFileSelectors(new FileSelector[] {selector});
+        return collection;
+    }
+
+    private byte[] readAllBytes(final java.io.InputStream in) throws IOException {
+        final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
+        final byte[] buffer = new byte[4096];
+        for (int n = in.read(buffer); n != -1; n = in.read(buffer)) {
+            out.write(buffer, 0, n);
+        }
+        return out.toByteArray();
+    }
+}