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 {