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 38f1684327 Do not block UI when project can't be found, fixes #3727 
(#6553)
38f1684327 is described below

commit 38f1684327144ba76899c84a47cf5de6ad38a17c
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Feb 13 09:27:31 2026 +0100

    Do not block UI when project can't be found, fixes #3727 (#6553)
---
 .../apache/hop/projects/gui/ProjectsGuiPlugin.java | 169 +++++++++++++++++++--
 .../org/apache/hop/projects/project/Project.java   |  14 +-
 .../apache/hop/projects/project/ProjectConfig.java |   7 +-
 .../apache/hop/projects/project/ProjectDialog.java |  38 +++--
 .../hop/projects/xp/HopGuiStartProjectLoad.java    | 102 +++++++++----
 5 files changed, 276 insertions(+), 54 deletions(-)

diff --git 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
index 36bbd62321..2f42f6b05a 100644
--- 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
+++ 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/gui/ProjectsGuiPlugin.java
@@ -32,6 +32,8 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 import org.apache.commons.lang.StringUtils;
@@ -270,12 +272,82 @@ public class ProjectsGuiPlugin {
       // Reset VFS filesystem to load additional configurations
       HopVfs.reset();
     } catch (Exception e) {
+      MissingProjectInfo missing = extractMissingProjectInfo(e);
+      if (missing != null) {
+        String displayName = missing.projectName != null ? missing.projectName 
: projectName;
+        MessageBox box = new MessageBox(HopGui.getInstance().getShell(), 
SWT.OK | SWT.ICON_WARNING);
+        box.setMessage(
+            "Project '"
+                + displayName
+                + "' could not be loaded because "
+                + missing.path
+                + " no longer exists.\n\nYou can update the path in the 
Project configuration.");
+        box.setText("Project not available");
+        box.open();
+        selectProjectInUiOnly(projectName);
+        return;
+      }
       throw new HopException("Error enabling project '" + projectName + "' in 
HopGui", e);
     }
   }
 
+  /** Result when the failure is due to a project home folder that does not 
exist. */
+  public static class MissingProjectInfo {
+    public final String projectName;
+    public final String path;
+
+    public MissingProjectInfo(String projectName, String path) {
+      this.projectName = projectName;
+      this.path = path;
+    }
+  }
+
+  /**
+   * Extracts the project name and path from an exception when the failure is 
due to "folder does
+   * not exist". Returns null if this is not a missing-folder error.
+   */
+  public static MissingProjectInfo extractMissingProjectInfo(Throwable t) {
+    Pattern withName =
+        Pattern.compile(
+            "Project '([^']+)' home folder '([^']+)' does not exist", 
Pattern.CASE_INSENSITIVE);
+    Pattern legacy =
+        Pattern.compile("Project home folder '([^']+)' does not exist", 
Pattern.CASE_INSENSITIVE);
+    while (t != null) {
+      String msg = t.getMessage();
+      if (msg != null) {
+        Matcher matcher = withName.matcher(msg);
+        if (matcher.find()) {
+          return new MissingProjectInfo(matcher.group(1), matcher.group(2));
+        }
+        matcher = legacy.matcher(msg);
+        if (matcher.find()) {
+          return new MissingProjectInfo(null, matcher.group(1));
+        }
+      }
+      t = t.getCause();
+    }
+    return null;
+  }
+
+  /**
+   * Extracts the project home path from an exception when the failure is due 
to "folder does not
+   * exist". Returns the path for use in a message, or null if this is not a 
missing-folder error.
+   */
+  public static String extractMissingProjectPath(Throwable t) {
+    MissingProjectInfo info = extractMissingProjectInfo(t);
+    return info != null ? info.path : null;
+  }
+
   private static void updateProjectToolItem(String projectName) {
     GuiToolbarWidgets statusWidgets = 
HopGui.getInstance().getStatusToolbarWidgets();
+    if (projectName == null || StringUtils.isEmpty(projectName)) {
+      statusWidgets.setToolbarItemText(ID_TOOLBAR_ITEM_PROJECT, "");
+      statusWidgets.setToolbarItemToolTip(
+          ID_TOOLBAR_ITEM_PROJECT,
+          BaseMessages.getString(PKG, 
"HopGui.Toolbar.Project.Select.Tooltip"));
+      HopNamespace.setNamespace(HopGui.DEFAULT_HOP_GUI_NAMESPACE);
+      return;
+    }
     ProjectsConfig config = ProjectsConfigSingleton.getConfig();
     ProjectConfig projectConfig = config.findProjectConfig(projectName);
     if (projectConfig != null) {
@@ -290,10 +362,31 @@ public class ProjectsGuiPlugin {
                 projectName,
                 projectHome,
                 projectConfig.getConfigFilename()));
+      } else {
+        statusWidgets.setToolbarItemText(ID_TOOLBAR_ITEM_PROJECT, projectName);
+        statusWidgets.setToolbarItemToolTip(
+            ID_TOOLBAR_ITEM_PROJECT, projectName + " (path not set - use Edit 
to configure)");
       }
     }
   }
 
+  /**
+   * Selects a project in the UI only (toolbar + namespace) without loading 
it. Use when the project
+   * failed to load so the user can still Edit or Delete it.
+   */
+  private static void selectProjectInUiOnly(String projectName) {
+    if (StringUtils.isEmpty(projectName)) {
+      return;
+    }
+    GuiToolbarWidgets statusWidgets = 
HopGui.getInstance().getStatusToolbarWidgets();
+    statusWidgets.setToolbarItemText(ID_TOOLBAR_ITEM_PROJECT, projectName);
+    statusWidgets.setToolbarItemToolTip(
+        ID_TOOLBAR_ITEM_PROJECT,
+        projectName + " (could not be loaded - use Edit to fix the path, or 
Delete to remove)");
+    HopNamespace.setNamespace(projectName);
+    updateEnvironmentToolItem("");
+  }
+
   private static void updateEnvironmentToolItem(String environmentName) {
     GuiToolbarWidgets statusWidgets = 
HopGui.getInstance().getStatusToolbarWidgets();
     if (Utils.isEmpty(environmentName)) {
@@ -320,23 +413,32 @@ public class ProjectsGuiPlugin {
   }
 
   public static List<String> getLastUsedProjects() {
-    // Initialize the list of last used projects
+    // Initialize the list of last used projects (defensive: one failing entry 
does not break the
+    // list)
     if (lastUsedProjects == null) {
       List<String> allProjectNames = 
ProjectsConfigSingleton.getConfig().listProjectConfigNames();
       lastUsedProjects = new LinkedList<>();
+      if (allProjectNames == null) {
+        allProjectNames = new ArrayList<>();
+      }
       try {
         AuditList auditList =
             AuditManager.getActive()
                 .retrieveList(HopGui.DEFAULT_HOP_GUI_NAMESPACE, 
LAST_USED_PROJECTS_AUDIT_TYPE);
 
-        for (String name : auditList.getNames()) {
-          if (allProjectNames.contains(name)) {
-            lastUsedProjects.add(name);
+        if (auditList != null && auditList.getNames() != null) {
+          for (String name : auditList.getNames()) {
+            if (StringUtils.isNotEmpty(name) && 
allProjectNames.contains(name)) {
+              lastUsedProjects.add(name);
+            }
           }
         }
       } catch (Exception e) {
         LogChannel.GENERAL.logError("Error loading last used projects", e);
-        lastUsedProjects.addAll(allProjectNames.subList(0, 
LAST_USED_PROJECTS_MAX_ENTRIES));
+        int end = Math.min(LAST_USED_PROJECTS_MAX_ENTRIES, 
allProjectNames.size());
+        if (end > 0) {
+          lastUsedProjects.addAll(allProjectNames.subList(0, end));
+        }
       }
     }
 
@@ -388,9 +490,27 @@ public class ProjectsGuiPlugin {
     if (projectConfig == null) {
       throw new HopException("The project with name '" + projectName + "' 
could not be found");
     }
-    Project project = projectConfig.loadProject(variables);
-
-    enableHopGuiProject(projectName, project, null);
+    try {
+      Project project = projectConfig.loadProject(variables);
+      enableHopGuiProject(projectName, project, null);
+    } catch (Exception e) {
+      MissingProjectInfo missing = extractMissingProjectInfo(e);
+      if (missing != null) {
+        String displayName = missing.projectName != null ? missing.projectName 
: projectName;
+        MessageBox box = new MessageBox(hopGui.getShell(), SWT.OK | 
SWT.ICON_WARNING);
+        box.setMessage(
+            "Project '"
+                + displayName
+                + "' could not be loaded because "
+                + missing.path
+                + " no longer exists.\n\nYou can update the path in the 
Project configuration.");
+        box.setText("Project not available");
+        box.open();
+        selectProjectInUiOnly(projectName);
+        return;
+      }
+      throw new HopException("Error enabling project '" + projectName + "'", 
e);
+    }
   }
 
   @GuiMenuElement(
@@ -413,7 +533,20 @@ public class ProjectsGuiPlugin {
     }
 
     try {
-      Project project = projectConfig.loadProject(hopGui.getVariables());
+      Project project;
+      try {
+        project = projectConfig.loadProject(hopGui.getVariables());
+      } catch (Exception loadEx) {
+        MissingProjectInfo missing = extractMissingProjectInfo(loadEx);
+        if (missing != null) {
+          // Don't show error again - it was already shown when switching to 
this project.
+          // Open dialog with empty project so user can fix the path.
+          project = new Project();
+        } else {
+          throw loadEx;
+        }
+      }
+
       String projectFolder = projectConfig.getProjectHome();
 
       ProjectDialog projectDialog =
@@ -663,6 +796,9 @@ public class ProjectsGuiPlugin {
     }
 
     ProjectConfig projectConfig = config.findProjectConfig(projectName);
+    if (projectConfig == null) {
+      return;
+    }
 
     // What is the last used environment?
     //
@@ -720,6 +856,21 @@ public class ProjectsGuiPlugin {
         hopGui.getLog().logError("Unable to find project '" + projectName + 
"'");
       }
     } catch (Exception e) {
+      MissingProjectInfo missing = extractMissingProjectInfo(e);
+      if (missing != null) {
+        String displayName = missing.projectName != null ? missing.projectName 
: projectName;
+        MessageBox box = new MessageBox(hopGui.getShell(), SWT.OK | 
SWT.ICON_WARNING);
+        box.setMessage(
+            "Project '"
+                + displayName
+                + "' could not be loaded because "
+                + missing.path
+                + " no longer exists.\n\nYou can update the path in the 
Project configuration.");
+        box.setText("Project not available");
+        box.open();
+        selectProjectInUiOnly(projectName);
+        return;
+      }
       new ErrorDialog(
           hopGui.getActiveShell(),
           BaseMessages.getString(PKG, 
"ProjectGuiPlugin.ChangeProject.Error.Dialog.Header"),
diff --git 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
index 13dc0aed2a..7301cbb23a 100644
--- 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
+++ 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
@@ -286,9 +286,19 @@ public class Project extends ConfigFile implements 
IConfigFile {
       projectsList.add(realParentProjectName);
       ProjectConfig projectConfig = 
config.findProjectConfig(realParentProjectName);
       if (projectConfig != null) {
-        Project parentProject = projectConfig.loadProject(variables);
+        Project parentProject;
+        try {
+          parentProject = projectConfig.loadProject(variables);
+        } catch (Exception e) {
+          LogChannel.GENERAL.logError(
+              "Could not load parent project '"
+                  + realParentProjectName
+                  + "'; continuing without it. Fix the project path in Project 
configuration.",
+              e);
+          parentProject = null;
+        }
         if (parentProject == null) {
-          // Can't be loaded, break out of the loop
+          // Can't be loaded, break out of the loop and continue with active 
project
           realParentProjectName = null;
         } else {
           // See if this project has a parent...
diff --git 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectConfig.java
 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectConfig.java
index c70d1746ca..97aa3ea80c 100644
--- 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectConfig.java
+++ 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectConfig.java
@@ -71,7 +71,12 @@ public class ProjectConfig {
       String actualHomeFolder = variables.resolve(getProjectHome());
       FileObject actualHome = HopVfs.getFileObject(actualHomeFolder);
       if (!actualHome.exists()) {
-        throw new HopException("Project home folder '" + actualHomeFolder + "' 
does not exist");
+        throw new HopException(
+            "Project '"
+                + getProjectName()
+                + "' home folder '"
+                + actualHomeFolder
+                + "' does not exist");
       }
       String actualConfigFilename = variables.resolve(getConfigFilename());
       String fullFilename = FilenameUtils.concat(actualHome.toString(), 
actualConfigFilename);
diff --git 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectDialog.java
 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectDialog.java
index 1a7b923c9c..c54cddf3ef 100644
--- 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectDialog.java
+++ 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/ProjectDialog.java
@@ -35,6 +35,7 @@ import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.projects.config.ProjectsConfig;
 import org.apache.hop.projects.config.ProjectsConfigSingleton;
+import org.apache.hop.projects.gui.ProjectsGuiPlugin;
 import org.apache.hop.projects.util.ProjectsUtil;
 import org.apache.hop.ui.core.ConstUi;
 import org.apache.hop.ui.core.PropsUi;
@@ -116,11 +117,16 @@ public class ProjectDialog extends Dialog {
     try {
       project.modifyVariables(variables, projectConfig, 
Collections.emptyList(), null);
     } catch (Exception e) {
-      new ErrorDialog(
-          parent,
-          BaseMessages.getString(PKG, 
"ProjectDialog.ProjectDefinitionError.Error.Dialog.Header"),
-          BaseMessages.getString(PKG, 
"ProjectDialog.ProjectDefinitionError.Error.Dialog.Message"),
-          e);
+      if (ProjectsGuiPlugin.extractMissingProjectPath(e) == null) {
+        new ErrorDialog(
+            parent,
+            BaseMessages.getString(PKG, 
"ProjectDialog.ProjectDefinitionError.Error.Dialog.Header"),
+            BaseMessages.getString(
+                PKG, 
"ProjectDialog.ProjectDefinitionError.Error.Dialog.Message"),
+            e);
+      }
+      // When the project folder does not exist, allow the dialog to open so 
the user can
+      // update the path in the configuration.
     }
   }
 
@@ -755,15 +761,29 @@ public class ProjectDialog extends Dialog {
       project.getDescribedVariables().add(variable);
     }
 
-    // Update the project to the right absolute configuration file
+    // Update the project to the right absolute configuration file (skip when 
folder missing so user
+    // can fix path)
     //
     if (StringUtils.isNotEmpty(projectConfig.getProjectHome())
         && StringUtils.isNotEmpty(projectConfig.configFilename)) {
-      
project.setConfigFilename(projectConfig.getActualProjectConfigFilename(variables));
+      try {
+        
project.setConfigFilename(projectConfig.getActualProjectConfigFilename(variables));
+      } catch (Exception e) {
+        if (ProjectsGuiPlugin.extractMissingProjectPath(e) == null) {
+          throw new HopException(e);
+        }
+        // Project folder does not exist yet; leave config filename unset so 
user can edit path
+      }
     }
 
-    // Check for infinite loops
+    // Check for infinite loops (skip when parent chain has missing folder so 
user can fix)
     //
-    project.verifyProjectsChain(projectConfig.getProjectName(), variables);
+    try {
+      project.verifyProjectsChain(projectConfig.getProjectName(), variables);
+    } catch (Exception e) {
+      if (ProjectsGuiPlugin.extractMissingProjectPath(e) == null) {
+        throw new HopException(e);
+      }
+    }
   }
 }
diff --git 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/HopGuiStartProjectLoad.java
 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/HopGuiStartProjectLoad.java
index 2e5ea6540c..ea3d812ec2 100644
--- 
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/HopGuiStartProjectLoad.java
+++ 
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/xp/HopGuiStartProjectLoad.java
@@ -17,6 +17,7 @@
 
 package org.apache.hop.projects.xp;
 
+import java.util.ArrayList;
 import java.util.List;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.exception.HopException;
@@ -34,7 +35,9 @@ import org.apache.hop.projects.project.Project;
 import org.apache.hop.projects.project.ProjectConfig;
 import org.apache.hop.projects.util.ProjectsUtil;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
+import org.apache.hop.ui.core.dialog.MessageBox;
 import org.apache.hop.ui.hopgui.HopGui;
+import org.eclipse.swt.SWT;
 
 @ExtensionPoint(
     id = "HopGuiStartProjectLoad",
@@ -57,47 +60,47 @@ public class HopGuiStartProjectLoad implements 
IExtensionPoint {
       if (ProjectsConfigSingleton.getConfig().isEnabled()) {
         logChannelInterface.logBasic("Projects enabled");
 
-        // What is the last used project?
-        //
-        String lastProjectName = null;
-
-        // Let's see in the audit logs what the last opened project is.
+        // Build list of candidate projects to try: last used first, then 
default.
+        // Defensive: try multiple recent projects so one failing does not 
block startup.
         //
+        List<String> candidateNames = new ArrayList<>();
         List<AuditEvent> auditEvents =
             AuditManager.findEvents(
                 ProjectsUtil.STRING_PROJECTS_AUDIT_GROUP,
                 ProjectsUtil.STRING_PROJECT_AUDIT_TYPE,
                 "open",
-                1,
+                15,
                 true);
-        if (auditEvents.isEmpty()) {
-          lastProjectName = config.getDefaultProject();
-        } else {
-          logChannelInterface.logDetailed(
-              "Audit events found for hop-gui/project : " + 
auditEvents.size());
-
-          AuditEvent lastEvent = auditEvents.get(0);
-          long eventTime = lastEvent.getDate().getTime();
-
-          lastProjectName = lastEvent.getName();
-          if (config.findProjectConfig(lastProjectName) == null) {
-            // The last existing project to open was not found.
-            //
-            lastProjectName = null;
+        for (AuditEvent event : auditEvents) {
+          String name = event.getName();
+          if (StringUtils.isNotEmpty(name)
+              && config.findProjectConfig(name) != null
+              && !candidateNames.contains(name)) {
+            candidateNames.add(name);
+          }
+        }
+        if (candidateNames.isEmpty() && config.getDefaultProject() != null) {
+          if (config.findProjectConfig(config.getDefaultProject()) != null) {
+            candidateNames.add(config.getDefaultProject());
           }
         }
 
-        if (StringUtils.isNotEmpty(lastProjectName)) {
-          ProjectConfig projectConfig = 
config.findProjectConfig(lastProjectName);
-          if (projectConfig != null) {
+        Exception firstFailure = null;
+        String firstFailedProjectName = null;
+        boolean enabled = false;
+
+        for (String lastProjectName : candidateNames) {
+          try {
+            ProjectConfig projectConfig = 
config.findProjectConfig(lastProjectName);
+            if (projectConfig == null) {
+              continue;
+            }
             Project project = projectConfig.loadProject(variables);
 
             logChannelInterface.logBasic("Enabling project : '" + 
lastProjectName + "'");
 
             LifecycleEnvironment lastEnvironment = null;
 
-            // What was the last environment for this project?
-            //
             List<AuditEvent> envEvents =
                 AuditManager.findEvents(
                     ProjectsUtil.STRING_PROJECTS_AUDIT_GROUP,
@@ -106,8 +109,6 @@ public class HopGuiStartProjectLoad implements 
IExtensionPoint {
                     100,
                     true);
 
-            // Find the last selected environment for this project.
-            //
             for (AuditEvent envEvent : envEvents) {
               LifecycleEnvironment environment = 
config.findEnvironment(envEvent.getName());
               if (environment != null && 
lastProjectName.equals(environment.getProjectName())) {
@@ -116,14 +117,49 @@ public class HopGuiStartProjectLoad implements 
IExtensionPoint {
               }
             }
 
-            // Set system variables for HOP_HOME, HOP_METADATA_FOLDER, ...
-            // Sets the namespace in HopGui to the name of the project.
-            //
             ProjectsGuiPlugin.enableHopGuiProject(lastProjectName, project, 
lastEnvironment);
-
-            // Don't open the files again in the HopGui startup code.
-            //
             hopGui.setOpeningLastFiles(false);
+            enabled = true;
+            break;
+          } catch (Exception e) {
+            if (firstFailure == null) {
+              firstFailure = e;
+              firstFailedProjectName = lastProjectName;
+            }
+            ProjectsGuiPlugin.MissingProjectInfo missing =
+                ProjectsGuiPlugin.extractMissingProjectInfo(e);
+            if (missing != null) {
+              logChannelInterface.logBasic(
+                  "Skipping project '"
+                      + lastProjectName
+                      + "': project folder no longer exists at "
+                      + missing.path);
+            }
+            // Continue to next candidate project.
+          }
+        }
+
+        if (!enabled && firstFailure != null) {
+          ProjectsGuiPlugin.MissingProjectInfo missing =
+              ProjectsGuiPlugin.extractMissingProjectInfo(firstFailure);
+          if (missing != null) {
+            String displayName =
+                missing.projectName != null ? missing.projectName : 
firstFailedProjectName;
+            MessageBox box = new MessageBox(hopGui.getActiveShell(), SWT.OK | 
SWT.ICON_WARNING);
+            box.setMessage(
+                "Project '"
+                    + displayName
+                    + "' could not be loaded because "
+                    + missing.path
+                    + " no longer exists.\n\nYou can update the path in the 
Project configuration.");
+            box.setText("Project not available");
+            box.open();
+          } else {
+            new ErrorDialog(
+                hopGui.getActiveShell(),
+                "Error",
+                "Error initializing the Projects system",
+                firstFailure);
           }
         }
       } else {

Reply via email to