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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 3ba4be3cf6 unzip action wildcard support, fixes #5943 (#7625)
3ba4be3cf6 is described below

commit 3ba4be3cf61fc7786fc12e76408a98437d108384
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Jul 24 17:25:38 2026 +0200

    unzip action wildcard support, fixes #5943 (#7625)
---
 .../hop/workflow/actions/unzip/ActionUnZip.java    |  29 ++--
 .../actions/unzip/WorkflowActionUnZipTest.java     | 159 +++++++++++++++++++++
 2 files changed, 177 insertions(+), 11 deletions(-)

diff --git 
a/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
 
b/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
index 778b7553b1..dc3b2adaa9 100644
--- 
a/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
+++ 
b/plugins/actions/unzip/src/main/java/org/apache/hop/workflow/actions/unzip/ActionUnZip.java
@@ -24,11 +24,11 @@ import java.io.OutputStream;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.List;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.commons.vfs2.AllFileSelector;
+import org.apache.commons.vfs2.FileName;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSelectInfo;
 import org.apache.commons.vfs2.FileSystemException;
@@ -482,10 +482,6 @@ public class ActionUnZip extends ActionBase implements 
Cloneable, IAction {
           if (!children[i].getType().equals(FileType.FOLDER)) {
             boolean unzip = true;
 
-            // Match RegExp against the file name only (like other file 
actions). getPath() is the
-            // full path from the FS root, so patterns such as 
"daily_.*\\.zip" never matched.
-            String sourceMatchName = children[i].getName().getBaseName();
-
             Pattern patternSource = null;
 
             if (!Utils.isEmpty(realWildcardSource)) {
@@ -494,8 +490,7 @@ public class ActionUnZip extends ActionBase implements 
Cloneable, IAction {
 
             // First see if the file matches the regular expression!
             if (patternSource != null) {
-              Matcher matcher = patternSource.matcher(sourceMatchName);
-              unzip = matcher.matches();
+              unzip = matchesWildcard(patternSource, children[i]);
             }
             if (unzip) {
               if (!unzipFile(
@@ -672,13 +667,11 @@ public class ActionUnZip extends ActionBase implements 
Cloneable, IAction {
               // First see if the file matches the regular expression!
               //
               if (pattern != null) {
-                Matcher matcher = pattern.matcher(item.getName().getURI());
-                getIt = matcher.matches();
+                getIt = matchesWildcard(pattern, item);
               }
 
               if (patternexclude != null) {
-                Matcher matcherexclude = 
patternexclude.matcher(item.getName().getURI());
-                getItexclude = matcherexclude.matches();
+                getItexclude = matchesWildcard(patternexclude, item);
               }
 
               boolean take = takeThisFile(item, newFileName);
@@ -975,6 +968,20 @@ public class ActionUnZip extends ActionBase implements 
Cloneable, IAction {
     return retval;
   }
 
+  /**
+   * Matches a compiled wildcard against a file. To stay consistent with the 
shared file readers
+   * ({@link org.apache.hop.core.fileinput.FileInputList}, used by Text File 
Input, Get File Names,
+   * ...) the pattern is tested against the file's base name, while also 
keeping the historical
+   * behavior of matching the full path/URI so that path-style patterns (those 
that include a
+   * directory prefix, as reported in issue #5943) keep working. A file 
matches when either form
+   * matches.
+   */
+  private static boolean matchesWildcard(Pattern pattern, FileObject file) {
+    FileName name = file.getName();
+    return pattern.matcher(name.getBaseName()).matches()
+        || pattern.matcher(name.getURI()).matches();
+  }
+
   private boolean takeThisFile(FileObject sourceFile, String destinationFile)
       throws FileSystemException {
     boolean retval = false;
diff --git 
a/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
 
b/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
index 1bb603d6c1..190b5490f1 100644
--- 
a/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
+++ 
b/plugins/actions/unzip/src/test/java/org/apache/hop/workflow/actions/unzip/WorkflowActionUnZipTest.java
@@ -17,17 +17,30 @@
 package org.apache.hop.workflow.actions.unzip;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
 import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 import org.apache.commons.vfs2.FileContent;
 import org.apache.commons.vfs2.FileName;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileType;
 import org.apache.hop.core.Result;
 import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.action.ActionMeta;
 import 
org.apache.hop.workflow.action.loadsave.WorkflowActionLoadSaveTestSupport;
+import org.apache.hop.workflow.engine.IWorkflowEngine;
+import org.apache.hop.workflow.engines.local.LocalWorkflowEngine;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.mockito.Mockito;
@@ -36,6 +49,20 @@ class WorkflowActionUnZipTest extends 
WorkflowActionLoadSaveTestSupport<ActionUn
   @RegisterExtension
   static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
 
+  /**
+   * Base-name source wildcard, aligned with the rest of Hop: all 
folder+wildcard file readers (Text
+   * File Input, Get File Names, ... via {@link 
org.apache.hop.core.fileinput.FileInputList}) match
+   * the wildcard against {@code getName().getBaseName()}.
+   */
+  private static final String SOURCE_WILDCARD_BASENAME = "geoip_.*\\.zip";
+
+  /**
+   * Path-style source wildcard (contains a directory separator), exactly as 
written in the last
+   * comment of issue #5943. It only matched before 2.18 (when the source 
wildcard was tested
+   * against the full path); it must keep working for backward compatibility.
+   */
+  private static final String SOURCE_WILDCARD_PATH = ".*/geoip_.*\\.zip";
+
   @Override
   protected Class<ActionUnZip> getActionClass() {
     return ActionUnZip.class;
@@ -89,4 +116,136 @@ class WorkflowActionUnZipTest extends 
WorkflowActionLoadSaveTestSupport<ActionUn
     Mockito.verify(sourceForMove, Mockito.times(1)).moveTo(Mockito.any());
     assertEquals(77L, moveResult.getBytesWrittenThisAction());
   }
+
+  /**
+   * Issue #5943: unzip a <b>folder</b> of archives selected with a base-name 
source wildcard. This
+   * is the convention shared with every other folder+wildcard reader in Hop 
(Text File Input, Get
+   * File Names, ... via {@link org.apache.hop.core.fileinput.FileInputList}), 
which matches the
+   * wildcard against the file's base name. Both {@code geoip_*.zip} archives 
should be extracted.
+   */
+  @Test
+  void unzipFolderWithSourceWildcardMatchesBaseName() throws Exception {
+    File sourceDir = createGeoipArchiveFolder();
+    File targetDir = new File(sourceDir, "extract");
+
+    Result result = runUnzipFolder(sourceDir, targetDir, 
SOURCE_WILDCARD_BASENAME, null);
+
+    assertEquals(0, result.getNrErrors(), "The action should not report 
errors");
+    assertEquals(
+        2,
+        result.getNrLinesWritten(),
+        "Both geoip_*.zip archives should have been matched and unzipped");
+    assertTrue(
+        new File(targetDir, "country.txt").exists(),
+        "country.txt from geoip_country.zip should have been extracted");
+    assertTrue(
+        new File(targetDir, "city.txt").exists(),
+        "city.txt from geoip_city.zip should have been extracted");
+  }
+
+  /**
+   * Issue #5943 regression: the same scenario using the path-style source 
wildcard from the last
+   * comment of the issue ({@link #SOURCE_WILDCARD_PATH}, which contains a 
directory separator).
+   * This pattern only matched before 2.18; the action now also matches the 
full path/URI so it
+   * keeps working. Without the fix this extracts nothing ("Nr unzipped files 
: 0") without an
+   * error.
+   */
+  @Test
+  void unzipFolderWithPathStyleSourceWildcard() throws Exception {
+    File sourceDir = createGeoipArchiveFolder();
+    File targetDir = new File(sourceDir, "extract");
+
+    Result result = runUnzipFolder(sourceDir, targetDir, SOURCE_WILDCARD_PATH, 
null);
+
+    assertEquals(0, result.getNrErrors(), "The action should not report 
errors");
+    assertEquals(
+        2,
+        result.getNrLinesWritten(),
+        "Both geoip_*.zip archives should have been matched by the path-style 
wildcard");
+    assertTrue(
+        new File(targetDir, "country.txt").exists(),
+        "country.txt from geoip_country.zip should have been extracted");
+    assertTrue(
+        new File(targetDir, "city.txt").exists(),
+        "city.txt from geoip_city.zip should have been extracted");
+  }
+
+  /**
+   * The wildcard selecting entries <i>inside</i> a zip ({@code wildcard}) is 
now matched against
+   * both the entry's base name and its full URI, consistent with the source 
wildcard. This verifies
+   * a plain base-name entry pattern extracts only the matching entry.
+   */
+  @Test
+  void unzipEntryWildcardMatchesBaseName() throws Exception {
+    File sourceDir = Files.createTempDirectory("unzip5943-entry").toFile();
+    sourceDir.deleteOnExit();
+    createZipWithEntries(
+        new File(sourceDir, "geoip.zip"),
+        new String[] {"country.txt", "city.txt"},
+        new String[] {"country-data", "city-data"});
+    File targetDir = new File(sourceDir, "extract");
+
+    // Select the single zip with a base-name source wildcard, and only the 
"country.txt" entry
+    // inside it with a base-name entry wildcard.
+    Result result = runUnzipFolder(sourceDir, targetDir, "geoip\\.zip", 
"country\\.txt");
+
+    assertEquals(0, result.getNrErrors(), "The action should not report 
errors");
+    assertTrue(
+        new File(targetDir, "country.txt").exists(),
+        "country.txt should have been extracted (matched the entry wildcard)");
+    assertFalse(
+        new File(targetDir, "city.txt").exists(),
+        "city.txt should have been filtered out by the entry wildcard");
+  }
+
+  /**
+   * Builds a temp folder with two {@code geoip_*.zip} archives, each holding 
one distinct entry.
+   */
+  private static File createGeoipArchiveFolder() throws java.io.IOException {
+    File sourceDir = Files.createTempDirectory("unzip5943-src").toFile();
+    sourceDir.deleteOnExit();
+    createZipWithEntries(
+        new File(sourceDir, "geoip_country.zip"),
+        new String[] {"country.txt"},
+        new String[] {"country-data"});
+    createZipWithEntries(
+        new File(sourceDir, "geoip_city.zip"),
+        new String[] {"city.txt"},
+        new String[] {"city-data"});
+    return sourceDir;
+  }
+
+  /** Configures and runs the unzip action over a folder of archives, 
returning its result. */
+  private static Result runUnzipFolder(
+      File sourceDir, File targetDir, String sourceWildcard, String 
entryWildcard) {
+    IWorkflowEngine<WorkflowMeta> workflow = new LocalWorkflowEngine(new 
WorkflowMeta());
+    workflow.setStopped(false);
+
+    ActionUnZip action = new ActionUnZip();
+    workflow.getWorkflowMeta().addAction(new ActionMeta(action));
+    action.setParentWorkflow(workflow);
+    action.setParentWorkflowMeta(workflow.getWorkflowMeta());
+
+    action.setZipFilename(sourceDir.getPath()); // Zip file name = a folder
+    action.setWildcardSource(sourceWildcard); // which archives in the folder
+    action.setWildcard(entryWildcard); // which entries inside each archive
+    action.setSourceDirectory(targetDir.getPath()); // Target directory (on 
screen)
+    action.setCreateFolder(true);
+    action.setIfFileExist(FileExistsEnum.SKIP);
+
+    return action.execute(new Result(), 0);
+  }
+
+  /** Creates a zip file containing the given text entries. */
+  private static void createZipWithEntries(File zipFile, String[] entryNames, 
String[] contents)
+      throws java.io.IOException {
+    try (OutputStream fos = new FileOutputStream(zipFile);
+        ZipOutputStream zos = new ZipOutputStream(fos)) {
+      for (int i = 0; i < entryNames.length; i++) {
+        zos.putNextEntry(new ZipEntry(entryNames[i]));
+        zos.write(contents[i].getBytes(StandardCharsets.UTF_8));
+        zos.closeEntry();
+      }
+    }
+  }
 }

Reply via email to