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 aa80d70746 issue #3153: add wildcards, exclude and subfolders to
Checks if files exist (#7810)
aa80d70746 is described below
commit aa80d70746c15840c6649673794b1a73f5aee3cd
Author: Matt Casters <[email protected]>
AuthorDate: Thu Aug 6 20:39:41 2026 +0200
issue #3153: add wildcards, exclude and subfolders to Checks if files exist
(#7810)
Allow the Checks if files exist action to specify per-row include/exclude
RegExp wildcards and include-subfolders, aligned with other file actions
and transforms, while keeping single-file File exists unchanged.
---
.../ROOT/pages/workflow/actions/filesexist.adoc | 9 +-
.../actions/filesexist/ActionFilesExist.java | 110 ++++++++++++++++++++-
.../actions/filesexist/ActionFilesExistDialog.java | 82 +++++++++++----
.../hop/workflow/actions/filesexist/FileItem.java | 46 ++++++++-
.../filesexist/messages/messages_en_US.properties | 10 +-
.../filesexist/WorkflowActionFilesExistTest.java | 80 ++++++++++++++-
.../src/test/resources/files-exist-action.xml | 7 +-
7 files changed, 309 insertions(+), 35 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/filesexist.adoc
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/filesexist.adoc
index f51f2a04ba..875d54be0f 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/filesexist.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/filesexist.adoc
@@ -26,11 +26,18 @@ under the License.
The `Checks if files exist` action verifies that the specified files exist.
The action returns `true` (success) if _all_ the listed files exist, `false`
(fail) if _any_ of those don't.
+You can point to individual files, or to folders and use regular-expression
wildcards (including an exclude wildcard) to verify that matching files are
present. Optionally include subfolders per file/folder entry when evaluating
wildcards.
+
== Options
[options="header", width="90%", cols="1,3"]
|===
|Option|Description
|Action name|The name of the workflow action. This name must be unique in a
single workflow.
-|Files/Folders|Specify the files and folders full paths to verify, one per
line. You can use variables like `{openvar}PROJECT_HOME{closevar}` in the path,
and they will be resolved.
+|Files/Folders|A table of files and folders to verify:
+
+* *File/Folder*: the full path of a file or folder. You can use variables like
`{openvar}PROJECT_HOME{closevar}` in the path.
+* *Wildcard (RegExp)*: optional regular expression. When the path is a folder,
at least one file matching this pattern must exist. When empty and no exclude
wildcard is set, the action only checks that the path itself exists (file or
folder).
+* *Exclude wildcard (RegExp)*: optional regular expression. Files matching
this pattern are ignored when evaluating a folder.
+* *Include subfolders*: when set to `Y`, also look for matching files in
subfolders of the specified folder.
|===
diff --git
a/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExist.java
b/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExist.java
index 86f06377b0..b92ebaa060 100644
---
a/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExist.java
+++
b/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExist.java
@@ -19,10 +19,15 @@ package org.apache.hop.workflow.actions.filesexist;
import java.util.ArrayList;
import java.util.List;
+import java.util.regex.Pattern;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSelectInfo;
+import org.apache.commons.vfs2.FileSelector;
+import org.apache.commons.vfs2.FileType;
import org.apache.hop.core.CheckResult;
+import org.apache.hop.core.Const;
import org.apache.hop.core.ICheckResult;
import org.apache.hop.core.Result;
import org.apache.hop.core.annotations.Action;
@@ -81,13 +86,55 @@ public class ActionFilesExist extends ActionBase implements
Cloneable, IAction {
if (fileItems != null) {
for (FileItem item : fileItems) {
- if (parentWorkflow.isStopped()) break;
+ if (parentWorkflow.isStopped()) {
+ break;
+ }
String path = resolve(item.getFileName());
+ String fileMask = resolve(item.getFileMask());
+ String excludeFileMask = resolve(item.getExcludeFileMask());
+ boolean includeSubfolders = item.isIncludeSubfolders();
+
try (FileObject file = HopVfs.getFileObject(path, getVariables())) {
+ if (!file.exists()) {
+ missingFiles++;
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(PKG,
"ActionFilesExist.File_Does_Not_Exist", path));
+ }
+ continue;
+ }
- if (file.exists()
- && file.isReadable()) { // TODO: is it needed to check file for
readability?
+ if (file.getType() == FileType.FOLDER
+ && (!Utils.isEmpty(fileMask) ||
!Utils.isEmpty(excludeFileMask))) {
+ // Folder with a wildcard: matching files must exist
+ FileObject[] matches =
+ file.findFiles(
+ new TextFileSelector(
+ file.toString(), fileMask, excludeFileMask,
includeSubfolders));
+ if (matches == null || matches.length == 0) {
+ missingFiles++;
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ActionFilesExist.NoMatchingFiles",
+ path,
+ Const.NVL(fileMask, ""),
+ Const.NVL(excludeFileMask, "")));
+ }
+ } else {
+ if (isDetailed()) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG,
+ "ActionFilesExist.MatchingFilesFound",
+ String.valueOf(matches.length),
+ path));
+ }
+ }
+ } else if (file.isReadable()) {
+ // File or folder without wildcards: path must exist (and be
readable)
if (isDetailed()) {
logDetailed(BaseMessages.getString(PKG,
"ActionFilesExist.File_Exists", path));
}
@@ -98,7 +145,6 @@ public class ActionFilesExist extends ActionBase implements
Cloneable, IAction {
BaseMessages.getString(PKG,
"ActionFilesExist.File_Does_Not_Exist", path));
}
}
-
} catch (Exception e) {
nrErrors++;
missingFiles++;
@@ -118,6 +164,62 @@ public class ActionFilesExist extends ActionBase
implements Cloneable, IAction {
return result;
}
+ private class TextFileSelector implements FileSelector {
+ private final String sourceFolder;
+ private final Pattern includePattern;
+ private final Pattern excludePattern;
+ private final boolean includeSubfolders;
+
+ public TextFileSelector(
+ String sourceFolder,
+ String fileWildcard,
+ String excludeWildcard,
+ boolean includeSubfolders) {
+ this.sourceFolder = sourceFolder;
+ this.includePattern = Utils.isEmpty(fileWildcard) ? null :
Pattern.compile(fileWildcard);
+ this.excludePattern =
+ Utils.isEmpty(excludeWildcard) ? null :
Pattern.compile(excludeWildcard);
+ this.includeSubfolders = includeSubfolders;
+ }
+
+ @Override
+ public boolean includeFile(FileSelectInfo info) {
+ try {
+ // Skip the base folder itself
+ if (info.getFile().toString().equals(sourceFolder)) {
+ return false;
+ }
+ if (info.getFile().getType() != FileType.FILE) {
+ return false;
+ }
+
+ // Only files in the base folder, or in sub-folders when enabled
+ boolean inBaseFolder =
info.getFile().getParent().equals(info.getBaseFolder());
+ if (!inBaseFolder && !includeSubfolders) {
+ return false;
+ }
+
+ String shortFilename = info.getFile().getName().getBaseName();
+ boolean matches = includePattern == null ||
includePattern.matcher(shortFilename).matches();
+ boolean excluded =
+ excludePattern != null &&
excludePattern.matcher(shortFilename).matches();
+ return matches && !excluded;
+ } catch (Exception e) {
+ logError(
+ BaseMessages.getString(
+ PKG,
+ "ActionFilesExist.ERROR_0004_IO_Exception",
+ info.getFile() + " : " + e.getMessage()));
+ return false;
+ }
+ }
+
+ @Override
+ public boolean traverseDescendents(FileSelectInfo info) {
+ return info.getDepth() == 0 || includeSubfolders;
+ }
+ }
+
@Override
public boolean isEvaluation() {
return true;
diff --git
a/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExistDialog.java
b/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExistDialog.java
index 375c05121f..539ce79d5c 100644
---
a/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExistDialog.java
+++
b/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/ActionFilesExistDialog.java
@@ -19,8 +19,13 @@ package org.apache.hop.workflow.actions.filesexist;
import java.util.ArrayList;
import java.util.List;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.exception.HopFileException;
+import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.dialog.BaseDialog;
@@ -45,6 +50,15 @@ import org.eclipse.swt.widgets.TableItem;
public class ActionFilesExistDialog extends ActionDialog {
private static final Class<?> PKG = ActionFilesExist.class;
+ private static final String[] FILETYPES =
+ new String[] {BaseMessages.getString(PKG, "System.FileType.AllFiles")};
+
+ private static final String[] YES_NO_COMBO =
+ new String[] {
+ BaseMessages.getString(PKG, "System.Combo.No"),
+ BaseMessages.getString(PKG, "System.Combo.Yes")
+ };
+
private ActionFilesExist action;
private boolean changed;
@@ -83,20 +97,28 @@ public class ActionFilesExistDialog extends ActionDialog {
BaseMessages.getString(PKG,
"ActionFilesExist.Fields.Argument.Label"),
ColumnInfo.COLUMN_TYPE_TEXT_BUTTON,
false),
+ new ColumnInfo(
+ BaseMessages.getString(PKG,
"ActionFilesExist.Fields.Wildcard.Label"),
+ ColumnInfo.COLUMN_TYPE_TEXT,
+ false),
+ new ColumnInfo(
+ BaseMessages.getString(PKG,
"ActionFilesExist.Fields.ExcludeWildcard.Label"),
+ ColumnInfo.COLUMN_TYPE_TEXT,
+ false),
+ new ColumnInfo(
+ BaseMessages.getString(PKG,
"ActionFilesExist.Fields.IncludeSubfolders.Label"),
+ ColumnInfo.COLUMN_TYPE_CCOMBO,
+ YES_NO_COMBO),
};
columns[0].setUsingVariables(true);
columns[0].setToolTip(BaseMessages.getString(PKG,
"ActionFilesExist.Fields.Column"));
- columns[0].setTextVarButtonSelectionListener(
- new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent arg0) {
- String filename = BaseDialog.presentFileDialog(shell, null, null,
true);
- if (filename != null) {
-
wFields.getActiveTableItem().setText(wFields.getActiveTableColumn(), filename);
- }
- }
- });
+ columns[0].setTextVarButtonSelectionListener(getFileSelectionAdapter());
+ columns[1].setUsingVariables(true);
+ columns[1].setToolTip(BaseMessages.getString(PKG,
"ActionFilesExist.Wildcard.Column"));
+ columns[2].setUsingVariables(true);
+ columns[2].setToolTip(BaseMessages.getString(PKG,
"ActionFilesExist.ExcludeWildcard.Column"));
+ columns[3].setToolTip(BaseMessages.getString(PKG,
"ActionFilesExist.IncludeSubfolders.Column"));
wFields =
new TableView(
@@ -114,7 +136,7 @@ public class ActionFilesExistDialog extends ActionDialog {
fdFields.right = new FormAttachment(100, 0);
fdFields.bottom = new FormAttachment(wCancel, -margin);
wFields.setLayoutData(fdFields);
- wFields.getTable().addListener(SWT.Resize, new ColumnsResizer(8, 92));
+ wFields.getTable().addListener(SWT.Resize, new ColumnsResizer(4, 40, 20,
20, 16));
getData();
focusActionName();
@@ -124,6 +146,27 @@ public class ActionFilesExistDialog extends ActionDialog {
return action;
}
+ protected SelectionAdapter getFileSelectionAdapter() {
+ return new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ try {
+ String path =
wFields.getActiveTableItem().getText(wFields.getActiveTableColumn());
+ FileObject fileObject = HopVfs.getFileObject(path);
+
+ path =
+ BaseDialog.presentFileDialog(
+ shell, null, variables, fileObject, new String[] {"*"},
FILETYPES, true);
+ if (path != null) {
+
wFields.getActiveTableItem().setText(wFields.getActiveTableColumn(), path);
+ }
+ } catch (HopFileException e) {
+ LogChannel.UI.logError("Error selecting file or directory", e);
+ }
+ }
+ };
+ }
+
/** Copy information from the meta-data input to the dialog fields. */
public void getData() {
if (action.getName() != null) {
@@ -133,12 +176,13 @@ public class ActionFilesExistDialog extends ActionDialog {
int i = 0;
for (FileItem item : action.getFileItems()) {
TableItem ti = wFields.table.getItem(i++);
- if (item.getFileName() != null) {
- ti.setText(1, item.getFileName());
- }
+ ti.setText(1, Const.NVL(item.getFileName(), ""));
+ ti.setText(2, Const.NVL(item.getFileMask(), ""));
+ ti.setText(3, Const.NVL(item.getExcludeFileMask(), ""));
+ ti.setText(4, item.isIncludeSubfolders() ? YES_NO_COMBO[1] :
YES_NO_COMBO[0]);
}
wFields.setRowNums();
- // wFields.optWidth(true);
+ wFields.optWidth(true);
}
@Override
@@ -165,9 +209,13 @@ public class ActionFilesExistDialog extends ActionDialog {
int numberOfItems = wFields.nrNonEmpty();
List<FileItem> items = new ArrayList<>();
for (int i = 0; i < numberOfItems; i++) {
- String path = wFields.getNonEmpty(i).getText(1);
+ TableItem tableItem = wFields.getNonEmpty(i);
+ String path = tableItem.getText(1);
+ String wildcard = tableItem.getText(2);
+ String excludeWildcard = tableItem.getText(3);
+ boolean includeSubfolders =
YES_NO_COMBO[1].equalsIgnoreCase(tableItem.getText(4));
if (!Utils.isEmpty(path)) {
- items.add(new FileItem(path));
+ items.add(new FileItem(path, wildcard, excludeWildcard,
includeSubfolders));
}
}
action.setFileItems(items);
diff --git
a/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/FileItem.java
b/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/FileItem.java
index 3d6d63b121..47ea6b1547 100644
---
a/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/FileItem.java
+++
b/plugins/actions/filesexist/src/main/java/org/apache/hop/workflow/actions/filesexist/FileItem.java
@@ -17,6 +17,7 @@
package org.apache.hop.workflow.actions.filesexist;
+import java.util.Objects;
import lombok.Getter;
import lombok.Setter;
import org.apache.hop.metadata.api.HopMetadataProperty;
@@ -25,26 +26,61 @@ import org.apache.hop.metadata.api.HopMetadataProperty;
@Getter
public class FileItem {
- /** File name */
+ /** File or folder name */
@HopMetadataProperty(key = "name")
private String fileName;
+ /** Wildcard or file mask (regular expression) */
+ @HopMetadataProperty(key = "filemask")
+ private String fileMask;
+
+ /** Wildcard or file mask to exclude (regular expression) */
+ @HopMetadataProperty(key = "exclude_filemask")
+ private String excludeFileMask;
+
+ /** Whether to include subfolders when evaluating wildcards on a folder */
+ @HopMetadataProperty(key = "include_subfolders")
+ private boolean includeSubfolders;
+
public FileItem() {}
public FileItem(String fileName) {
+ this(fileName, null, null, false);
+ }
+
+ public FileItem(String fileName, String fileMask) {
+ this(fileName, fileMask, null, false);
+ }
+
+ public FileItem(String fileName, String fileMask, String excludeFileMask) {
+ this(fileName, fileMask, excludeFileMask, false);
+ }
+
+ public FileItem(
+ String fileName, String fileMask, String excludeFileMask, boolean
includeSubfolders) {
this.fileName = fileName;
+ this.fileMask = fileMask;
+ this.excludeFileMask = excludeFileMask;
+ this.includeSubfolders = includeSubfolders;
}
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
FileItem item = (FileItem) o;
- return fileName.equals(item.fileName);
+ return includeSubfolders == item.includeSubfolders
+ && Objects.equals(fileName, item.fileName)
+ && Objects.equals(fileMask, item.fileMask)
+ && Objects.equals(excludeFileMask, item.excludeFileMask);
}
@Override
public int hashCode() {
- return java.util.Objects.hash(fileName);
+ return Objects.hash(fileName, fileMask, excludeFileMask,
includeSubfolders);
}
}
diff --git
a/plugins/actions/filesexist/src/main/resources/org/apache/hop/workflow/actions/filesexist/messages/messages_en_US.properties
b/plugins/actions/filesexist/src/main/resources/org/apache/hop/workflow/actions/filesexist/messages/messages_en_US.properties
index 9e9fc4bb6d..ee7fe91a15 100644
---
a/plugins/actions/filesexist/src/main/resources/org/apache/hop/workflow/actions/filesexist/messages/messages_en_US.properties
+++
b/plugins/actions/filesexist/src/main/resources/org/apache/hop/workflow/actions/filesexist/messages/messages_en_US.properties
@@ -18,12 +18,20 @@
ActionFilesExist.CheckResult.NothingToCheck=There's nothing to check
ActionFilesExist.Description=Checks if files exists
ActionFilesExist.ERROR_0004_IO_Exception=Unexpected error checking filename
existance: {0}
+ActionFilesExist.ExcludeWildcard.Column=Enter a regular expression to exclude
files (RegExp)
ActionFilesExist.Fields.Argument.Label=File/Folder
-ActionFilesExist.Fields.Column=Specify here file to check
+ActionFilesExist.Fields.Column=Specify a file or folder to check
+ActionFilesExist.Fields.ExcludeWildcard.Label=Exclude wildcard (RegExp)
+ActionFilesExist.Fields.IncludeSubfolders.Label=Include subfolders
ActionFilesExist.Fields.Label=Files/Folders
+ActionFilesExist.Fields.Wildcard.Label=Wildcard (RegExp)
ActionFilesExist.File_Does_Not_Exist=File [{0}] does not exist\!
ActionFilesExist.File_Exists=File [{0}] exists.
+ActionFilesExist.IncludeSubfolders.Column=Include files in subfolders when
evaluating wildcards on a folder
ActionFilesExist.Keyword=exist,check,verify,presence,filesystem
+ActionFilesExist.MatchingFilesFound=Found {0} matching file(s) in [{1}]
ActionFilesExist.Name=Checks if files exist
ActionFilesExist.Name.Default=Files exist
+ActionFilesExist.NoMatchingFiles=No files matching include [{1}] / exclude
[{2}] were found in folder [{0}]
ActionFilesExist.Title=Checks if files exist
+ActionFilesExist.Wildcard.Column=Specify a wildcard (RegExp) when the path is
a folder
diff --git
a/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
b/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
index 21f3cdf252..248e139cd3 100644
---
a/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
+++
b/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
@@ -23,8 +23,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.List;
+import org.apache.commons.vfs2.FileObject;
import org.apache.hop.core.Result;
import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.utils.TestUtils;
import org.apache.hop.workflow.WorkflowMeta;
import org.apache.hop.workflow.action.ActionMeta;
@@ -41,6 +43,7 @@ class WorkflowActionFilesExistTest {
private String existingFile1;
private String existingFile2;
+ private String folderPath;
@BeforeAll
static void setUpBeforeClass() {
@@ -48,7 +51,7 @@ class WorkflowActionFilesExistTest {
}
@BeforeEach
- void setUp() {
+ void setUp() throws Exception {
workflow = new LocalWorkflowEngine(new WorkflowMeta());
action = new ActionFilesExist();
@@ -59,10 +62,18 @@ class WorkflowActionFilesExistTest {
workflow.setStopped(false);
- existingFile1 =
- TestUtils.createRamFile(getClass().getSimpleName() +
"/existingFile1.ext", action);
- existingFile2 =
- TestUtils.createRamFile(getClass().getSimpleName() +
"/existingFile2.ext", action);
+ String base = getClass().getSimpleName();
+ existingFile1 = TestUtils.createRamFile(base + "/existingFile1.ext",
action);
+ existingFile2 = TestUtils.createRamFile(base + "/existingFile2.ext",
action);
+
+ folderPath = "ram://" + base + "/folder";
+ try (FileObject folder = HopVfs.getFileObject(folderPath, action)) {
+ folder.createFolder();
+ }
+ TestUtils.createRamFile(base + "/folder/match.txt", action);
+ TestUtils.createRamFile(base + "/folder/skip.tmp", action);
+ TestUtils.createRamFile(base + "/folder/other.log", action);
+ TestUtils.createRamFile(base + "/folder/sub/nested.txt", action);
}
@Test
@@ -72,7 +83,11 @@ class WorkflowActionFilesExistTest {
"/files-exist-action.xml", ActionFilesExist.class);
assertEquals("/folder", meta.getFileItems().get(0).getFileName());
+ assertEquals(".*\\.txt", meta.getFileItems().get(0).getFileMask());
+ assertEquals(".*\\.tmp", meta.getFileItems().get(0).getExcludeFileMask());
+ assertTrue(meta.getFileItems().get(0).isIncludeSubfolders());
assertEquals("/archive.zip", meta.getFileItems().get(1).getFileName());
+ assertFalse(meta.getFileItems().get(1).isIncludeSubfolders());
assertEquals(2, meta.getFileItems().size());
}
@@ -120,4 +135,59 @@ class WorkflowActionFilesExistTest {
Result res = action.execute(new Result(), 0);
assertFalse(res.isResult());
}
+
+ @Test
+ void testFolderExistsWithoutWildcard() {
+ action.setFileItems(List.of(new FileItem(folderPath)));
+
+ Result res = action.execute(new Result(), 0);
+ assertTrue(res.isResult(), "Folder path without wildcards should succeed
when folder exists");
+ }
+
+ @Test
+ void testFolderWithMatchingWildcard() {
+ action.setFileItems(List.of(new FileItem(folderPath, ".*\\.txt")));
+
+ Result res = action.execute(new Result(), 0);
+ assertTrue(res.isResult(), "Matching files in folder should succeed");
+ }
+
+ @Test
+ void testFolderWithNonMatchingWildcard() {
+ action.setFileItems(List.of(new FileItem(folderPath, ".*\\.csv")));
+
+ Result res = action.execute(new Result(), 0);
+ assertFalse(res.isResult(), "No matching files should fail");
+ }
+
+ @Test
+ void testFolderWithExcludeWildcard() {
+ // Only skip.tmp would match .*, but exclude removes .tmp — match.txt and
other.log remain
+ action.setFileItems(List.of(new FileItem(folderPath, ".*", ".*\\.tmp")));
+
+ Result res = action.execute(new Result(), 0);
+ assertTrue(res.isResult(), "Files remaining after exclude should succeed");
+ }
+
+ @Test
+ void testFolderWithExcludeRemovesAllMatches() {
+ // Include only .tmp, then exclude .tmp → nothing left
+ action.setFileItems(List.of(new FileItem(folderPath, ".*\\.tmp",
".*\\.tmp")));
+
+ Result res = action.execute(new Result(), 0);
+ assertFalse(res.isResult(), "All matches excluded should fail");
+ }
+
+ @Test
+ void testIncludeSubfoldersPerRow() {
+ // nested.txt is in a subfolder; without includeSubfolders it is not found
+ action.setFileItems(List.of(new FileItem(folderPath, "nested\\.txt", null,
false)));
+
+ Result withoutSub = action.execute(new Result(), 0);
+ assertFalse(withoutSub.isResult(), "Nested file should not match without
subfolders");
+
+ action.setFileItems(List.of(new FileItem(folderPath, "nested\\.txt", null,
true)));
+ Result withSub = action.execute(new Result(), 0);
+ assertTrue(withSub.isResult(), "Nested file should match with include
subfolders");
+ }
}
diff --git
a/plugins/actions/filesexist/src/test/resources/files-exist-action.xml
b/plugins/actions/filesexist/src/test/resources/files-exist-action.xml
index f75c523865..949c46ec79 100644
--- a/plugins/actions/filesexist/src/test/resources/files-exist-action.xml
+++ b/plugins/actions/filesexist/src/test/resources/files-exist-action.xml
@@ -21,17 +21,20 @@
<description/>
<type>FILES_EXIST</type>
<attributes/>
- <filename/>
<fields>
<field>
<name>/folder</name>
+ <filemask>.*\.txt</filemask>
+ <exclude_filemask>.*\.tmp</exclude_filemask>
+ <include_subfolders>Y</include_subfolders>
</field>
<field>
<name>/archive.zip</name>
+ <include_subfolders>N</include_subfolders>
</field>
</fields>
<parallel>N</parallel>
<xloc>160</xloc>
<yloc>32</yloc>
<attributes_hac/>
-</action>
\ No newline at end of file
+</action>