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 82cb0b5d43 issue #7341 : "Caching File Location” result in an error if 
the destination folder does not exist (#7369)
82cb0b5d43 is described below

commit 82cb0b5d439e42e76c4d322a4eb6afc6d4d94df0
Author: Matt Casters <[email protected]>
AuthorDate: Tue Jun 30 13:29:42 2026 +0200

    issue #7341 : "Caching File Location” result in an error if the destination 
folder does not exist (#7369)
---
 .../execution-information-location.adoc            |  2 +
 .../caching/CachingFileExecutionInfoLocation.java  | 24 +++++++
 .../execution/local/FileExecutionInfoLocation.java | 36 +++++++++-
 .../caching/messages/messages_en_US.properties     |  2 +
 .../local/messages/messages_en_US.properties       |  2 +
 .../execution/messages/messages_en_US.properties   |  2 +
 .../CachingFileExecutionInfoLocationTest.java      | 83 ++++++++++++++++++++++
 .../local/FileExecutionInfoLocationTest.java       | 75 +++++++++++++++++++
 8 files changed, 225 insertions(+), 1 deletion(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/execution-information-location.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/execution-information-location.adoc
index e3121f9da7..29b76e5a3d 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/execution-information-location.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/execution-information-location.adoc
@@ -54,6 +54,7 @@ With a file location you can write execution information to 
files in a path on t
 Options:
 
 * *Root folder*: the folder to write the execution information to.
+* *Create folder?*: automatically create the root folder if it doesn't exist.
 
 === Caching File Location
 
@@ -67,6 +68,7 @@ Certain navigation operations in the GUI, for example to find 
related executions
 Options:
 
 * *Root folder*: the folder in which execution information is stored in JSON 
files
+* *Create folder?*: automatically create the root folder if it doesn't exist.
 * *Persistence delay*: This is the maximum time to wait before execution 
information is written to disk, expressed in milliseconds.
 * *Maximum cache age*: This is the maximum time to keep execution information 
around in memory, before clearing it out, expressed in milliseconds.  It's 
advised to keep this below the expected duration of your workflow or pipeline.
 
diff --git 
a/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
 
b/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
index eb890a49e8..3dbd8d2e97 100644
--- 
a/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
+++ 
b/engine/src/main/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocation.java
@@ -26,6 +26,7 @@ import java.util.Date;
 import java.util.Set;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.vfs2.AllFileSelector;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSelectInfo;
@@ -64,16 +65,28 @@ public class CachingFileExecutionInfoLocation extends 
BaseCachingExecutionInfoLo
   @HopMetadataProperty
   protected String rootFolder;
 
+  @GuiWidgetElement(
+      id = "createParentFolder",
+      order = "015",
+      parentId = ExecutionInfoLocation.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.CHECKBOX,
+      toolTip = 
"i18n::CachingFileExecutionInfoLocation.CreateParentFolder.Tooltip",
+      label = 
"i18n::CachingFileExecutionInfoLocation.CreateParentFolder.Label")
+  @HopMetadataProperty
+  protected boolean createParentFolder;
+
   protected String actualRootFolder;
 
   public CachingFileExecutionInfoLocation() {
     super();
     this.actualRootFolder = null;
+    this.createParentFolder = true;
   }
 
   public CachingFileExecutionInfoLocation(CachingFileExecutionInfoLocation 
location) {
     super(location);
     this.rootFolder = location.rootFolder;
+    this.createParentFolder = location.createParentFolder;
     this.actualRootFolder = location.actualRootFolder;
   }
 
@@ -89,6 +102,17 @@ public class CachingFileExecutionInfoLocation extends 
BaseCachingExecutionInfoLo
     //
     actualRootFolder = variables.resolve(rootFolder);
 
+    if (createParentFolder && StringUtils.isNotEmpty(actualRootFolder)) {
+      try {
+        FileObject folder = HopVfs.getFileObject(actualRootFolder);
+        if (!folder.exists()) {
+          folder.createFolder();
+        }
+      } catch (Exception e) {
+        throw new HopException("Error creating root folder " + 
actualRootFolder, e);
+      }
+    }
+
     super.initialize(variables, metadataProvider);
   }
 
diff --git 
a/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
 
b/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
index 40757a7bec..71f1103c7d 100644
--- 
a/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
+++ 
b/engine/src/main/java/org/apache/hop/execution/local/FileExecutionInfoLocation.java
@@ -82,20 +82,34 @@ public class FileExecutionInfoLocation implements 
IExecutionInfoLocation {
   @HopMetadataProperty
   protected String rootFolder;
 
+  @GuiWidgetElement(
+      id = "createParentFolder",
+      order = "015",
+      parentId = ExecutionInfoLocation.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.CHECKBOX,
+      toolTip = "i18n::LocalExecutionInfoLocation.CreateParentFolder.Tooltip",
+      label = "i18n::LocalExecutionInfoLocation.CreateParentFolder.Label")
+  @HopMetadataProperty
+  protected boolean createParentFolder;
+
   private IVariables variables;
 
-  public FileExecutionInfoLocation() {}
+  public FileExecutionInfoLocation() {
+    this.createParentFolder = true;
+  }
 
   public FileExecutionInfoLocation(String rootFolder) {
     this.pluginId = "local-folder";
     this.pluginName = "File location";
     this.rootFolder = rootFolder;
+    this.createParentFolder = true;
   }
 
   public FileExecutionInfoLocation(FileExecutionInfoLocation location) {
     this.pluginId = location.pluginId;
     this.pluginName = location.pluginName;
     this.rootFolder = location.rootFolder;
+    this.createParentFolder = location.createParentFolder;
   }
 
   public FileExecutionInfoLocation clone() {
@@ -106,6 +120,18 @@ public class FileExecutionInfoLocation implements 
IExecutionInfoLocation {
   public void initialize(IVariables variables, IHopMetadataProvider 
metadataProvider)
       throws HopException {
     this.variables = variables;
+
+    if (createParentFolder && StringUtils.isNotEmpty(rootFolder)) {
+      String actualRootFolder = variables.resolve(rootFolder);
+      try {
+        FileObject folder = HopVfs.getFileObject(actualRootFolder);
+        if (!folder.exists()) {
+          folder.createFolder();
+        }
+      } catch (Exception e) {
+        throw new HopException("Error creating root folder " + 
actualRootFolder, e);
+      }
+    }
   }
 
   @Override
@@ -647,6 +673,14 @@ public class FileExecutionInfoLocation implements 
IExecutionInfoLocation {
     this.rootFolder = rootFolder;
   }
 
+  public boolean isCreateParentFolder() {
+    return createParentFolder;
+  }
+
+  public void setCreateParentFolder(boolean createParentFolder) {
+    this.createParentFolder = createParentFolder;
+  }
+
   private static class ExecutionIdAndDate {
     public String id;
     public Date startDate;
diff --git 
a/engine/src/main/resources/org/apache/hop/execution/caching/messages/messages_en_US.properties
 
b/engine/src/main/resources/org/apache/hop/execution/caching/messages/messages_en_US.properties
index 7232fa795d..4c81439b4f 100644
--- 
a/engine/src/main/resources/org/apache/hop/execution/caching/messages/messages_en_US.properties
+++ 
b/engine/src/main/resources/org/apache/hop/execution/caching/messages/messages_en_US.properties
@@ -21,4 +21,6 @@ CachingFileExecutionInfoLocation.PersistenceDelay.Label = The 
persistence delay
 CachingFileExecutionInfoLocation.PersistenceDelay.Tooltip = The maximum time 
to wait before persisting data in the cache to file
 CachingFileExecutionInfoLocation.MaxCacheAge.Label = Maximum cache entry age 
(in ms)
 CachingFileExecutionInfoLocation.MaxCacheAge.Tooltip = The maximum time to 
keep a cache entry around without it being read or written to
+CachingFileExecutionInfoLocation.CreateParentFolder.Label = Create folder?
+CachingFileExecutionInfoLocation.CreateParentFolder.Tooltip = Check this 
option to create the specified folder if it doesn't exist.
 
diff --git 
a/engine/src/main/resources/org/apache/hop/execution/local/messages/messages_en_US.properties
 
b/engine/src/main/resources/org/apache/hop/execution/local/messages/messages_en_US.properties
index 004dd41731..854a7d06e1 100644
--- 
a/engine/src/main/resources/org/apache/hop/execution/local/messages/messages_en_US.properties
+++ 
b/engine/src/main/resources/org/apache/hop/execution/local/messages/messages_en_US.properties
@@ -18,3 +18,5 @@
 
 LocalExecutionInfoLocation.RootFolder.Label = Root folder
 LocalExecutionInfoLocation.RootFolder.Tooltip = The root folder to store 
execution information in.
+LocalExecutionInfoLocation.CreateParentFolder.Label = Create folder?
+LocalExecutionInfoLocation.CreateParentFolder.Tooltip = Check this option to 
create the specified folder if it doesn't exist.
diff --git 
a/engine/src/main/resources/org/apache/hop/execution/messages/messages_en_US.properties
 
b/engine/src/main/resources/org/apache/hop/execution/messages/messages_en_US.properties
index b119d4501d..500c8930cd 100644
--- 
a/engine/src/main/resources/org/apache/hop/execution/messages/messages_en_US.properties
+++ 
b/engine/src/main/resources/org/apache/hop/execution/messages/messages_en_US.properties
@@ -18,5 +18,7 @@
 
 LocalExecutionInfoLocation.RootFolder.Label = Root folder
 LocalExecutionInfoLocation.RootFolder.Tooltip = This is the location where 
execution information will be stored in files and sub-folders.
+LocalExecutionInfoLocation.CreateParentFolder.Label = Create folder?
+LocalExecutionInfoLocation.CreateParentFolder.Tooltip = Check this option to 
create the specified folder if it doesn't exist.
 ExecutionInfoLocation.name=Execution Information Location
 ExecutionInfoLocation.description=Describes where Hop can send execution 
information to
diff --git 
a/engine/src/test/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocationTest.java
 
b/engine/src/test/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocationTest.java
new file mode 100644
index 0000000000..405cb5330c
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/execution/caching/CachingFileExecutionInfoLocationTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.hop.execution.caching;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+import org.apache.commons.io.FileUtils;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class CachingFileExecutionInfoLocationTest {
+
+  private Path tempDir;
+
+  @BeforeEach
+  void setUp() throws Exception {
+    tempDir = Files.createTempDirectory("hop-caching-exec-info-test");
+  }
+
+  @AfterEach
+  void tearDown() throws Exception {
+    if (tempDir != null) {
+      FileUtils.deleteDirectory(tempDir.toFile());
+    }
+  }
+
+  @Test
+  void testInitializeCreateFolderTrue() throws Exception {
+    Path targetDir = tempDir.resolve(UUID.randomUUID().toString());
+    assertFalse(Files.exists(targetDir));
+
+    CachingFileExecutionInfoLocation location = new 
CachingFileExecutionInfoLocation();
+    location.setRootFolder(targetDir.toAbsolutePath().toString());
+    assertTrue(location.isCreateParentFolder());
+
+    location.initialize(new Variables(), null);
+    try {
+      assertTrue(Files.exists(targetDir));
+    } finally {
+      location.close();
+    }
+  }
+
+  @Test
+  void testInitializeCreateFolderFalse() throws Exception {
+    Path targetDir = tempDir.resolve(UUID.randomUUID().toString());
+    assertFalse(Files.exists(targetDir));
+
+    CachingFileExecutionInfoLocation location = new 
CachingFileExecutionInfoLocation();
+    location.setRootFolder(targetDir.toAbsolutePath().toString());
+    location.setCreateParentFolder(false);
+    assertFalse(location.isCreateParentFolder());
+
+    location.initialize(new Variables(), null);
+    try {
+      assertFalse(Files.exists(targetDir));
+    } finally {
+      location.close();
+    }
+  }
+}
diff --git 
a/engine/src/test/java/org/apache/hop/execution/local/FileExecutionInfoLocationTest.java
 
b/engine/src/test/java/org/apache/hop/execution/local/FileExecutionInfoLocationTest.java
new file mode 100644
index 0000000000..47256fefe4
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/execution/local/FileExecutionInfoLocationTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.hop.execution.local;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+import org.apache.commons.io.FileUtils;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class FileExecutionInfoLocationTest {
+
+  private Path tempDir;
+
+  @BeforeEach
+  void setUp() throws Exception {
+    tempDir = Files.createTempDirectory("hop-exec-info-test");
+  }
+
+  @AfterEach
+  void tearDown() throws Exception {
+    if (tempDir != null) {
+      FileUtils.deleteDirectory(tempDir.toFile());
+    }
+  }
+
+  @Test
+  void testInitializeCreateFolderTrue() throws Exception {
+    Path targetDir = tempDir.resolve(UUID.randomUUID().toString());
+    assertFalse(Files.exists(targetDir));
+
+    FileExecutionInfoLocation location =
+        new FileExecutionInfoLocation(targetDir.toAbsolutePath().toString());
+    assertTrue(location.isCreateParentFolder());
+
+    location.initialize(new Variables(), null);
+    assertTrue(Files.exists(targetDir));
+  }
+
+  @Test
+  void testInitializeCreateFolderFalse() throws Exception {
+    Path targetDir = tempDir.resolve(UUID.randomUUID().toString());
+    assertFalse(Files.exists(targetDir));
+
+    FileExecutionInfoLocation location =
+        new FileExecutionInfoLocation(targetDir.toAbsolutePath().toString());
+    location.setCreateParentFolder(false);
+    assertFalse(location.isCreateParentFolder());
+
+    location.initialize(new Variables(), null);
+    assertFalse(Files.exists(targetDir));
+  }
+}

Reply via email to