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();
+ }
+ }
+ }
}