From 0138b5924ff14ef709398bf878dbb9bf2a0b7c33 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 23 Sep 2026 13:05:17 +0000
Subject: [PATCH] [#1030] Keep the setup log out of the way of start-ds, and say when it is gone (#1032)

---
 opendj-server-legacy/src/main/java/org/opends/quicksetup/Launcher.java |  141 +++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 117 insertions(+), 24 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/quicksetup/Launcher.java b/opendj-server-legacy/src/main/java/org/opends/quicksetup/Launcher.java
index 1fc34e7..c0622ee 100644
--- a/opendj-server-legacy/src/main/java/org/opends/quicksetup/Launcher.java
+++ b/opendj-server-legacy/src/main/java/org/opends/quicksetup/Launcher.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2008-2009 Sun Microsystems, Inc.
  * Portions Copyright 2013-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.quicksetup;
 
@@ -21,7 +22,9 @@
 import static org.opends.messages.QuickSetupMessages.*;
 import static org.opends.server.util.DynamicConstants.*;
 
+import java.io.File;
 import java.io.PrintStream;
+import java.util.function.Supplier;
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -40,8 +43,16 @@
   /** Arguments with which this launcher was invoked. */
   protected final String[] args;
 
-  /** The temporary log file which will be kept if an error occurs. */
-  protected final TempLogFile tempLogFile;
+  private final String tempLogFilePrefix;
+  private final File tempLogFileDirectory;
+  /**
+   * The temporary log file which will be kept if an error occurs; see {@link #getTempLogFile()}.
+   * Volatile because the GUI road asks for it from the thread that runs the splash screen and
+   * the roads after it read it from the main thread.
+   */
+  private volatile TempLogFile tempLogFile;
+  /** Why the GUI did not come up, kept until there is a log to write it to. */
+  private volatile Throwable guiLaunchFailure;
 
   /**
    * Creates a Launcher.
@@ -52,11 +63,79 @@
    *          temporary log file path where messages will be logged
    */
   public Launcher(final String[] args, final String tempLogFilePrefix) {
+    this(args, tempLogFilePrefix, null);
+  }
+
+  /**
+   * Creates a Launcher whose temporary log file lives in the given directory.
+   *
+   * @param args
+   *          String[] of argument passes from the command line
+   * @param tempLogFilePrefix
+   *          temporary log file path where messages will be logged
+   * @param tempLogFileDirectory
+   *          the directory to create the temporary log file in, or {@code null} for the OS
+   *          temporary directory
+   */
+  public Launcher(final String[] args, final String tempLogFilePrefix, final File tempLogFileDirectory) {
     if (args == null) {
       throw new IllegalArgumentException("args cannot be null");
     }
     this.args = args;
-    this.tempLogFile = TempLogFile.newTempLogFile(tempLogFilePrefix);
+    this.tempLogFilePrefix = tempLogFilePrefix;
+    this.tempLogFileDirectory = tempLogFileDirectory;
+  }
+
+  /**
+   * The temporary log file of this launcher, created the first time it is asked for.
+   * <p>
+   * Creating it costs a file - and, with a directory of the caller's choosing, the directory
+   * as well - that nothing removes afterwards unless the operation succeeds. So it is created
+   * on the first road that can fail an operation and not before: {@code --help},
+   * {@code --version}, a usage error and the other roads that attempt nothing leave no log
+   * behind (issue #1030).
+   *
+   * @return the temporary log file, creating it if this is the first call.
+   */
+  protected synchronized TempLogFile getTempLogFile() {
+    if (tempLogFile == null) {
+      tempLogFile = TempLogFile.newTempLogFile(tempLogFilePrefix, tempLogFileDirectory);
+      logGuiLaunchFailure();
+    }
+    return tempLogFile;
+  }
+
+  /**
+   * Whether there is a log to name, without creating one to answer.
+   *
+   * @return {@code true} if a temporary log file has been created and can be used to log
+   *         messages.
+   */
+  protected boolean hasTempLogFile() {
+    return tempLogFile != null && tempLogFile.isEnabled();
+  }
+
+  /** Writes the reason the GUI did not come up, now that there is a log to hold it. */
+  private void logGuiLaunchFailure() {
+    Throwable failure = guiLaunchFailure;
+    if (failure == null) {
+      return;
+    }
+    logger.warn(LocalizableMessage.raw("Error launching GUI: " + failure));
+    StringBuilder buf = new StringBuilder();
+    while (failure != null)
+    {
+      for (StackTraceElement aStack : failure.getStackTrace()) {
+        buf.append(aStack).append("\n");
+      }
+
+      failure = failure.getCause();
+      if (failure != null)
+      {
+        buf.append("Root cause:\n");
+      }
+    }
+    logger.warn(LocalizableMessage.raw(buf));
   }
 
   /**
@@ -194,29 +273,16 @@
       {
         try
         {
-          SplashScreen.main(tempLogFile, args);
+          startSplashScreen(Launcher.this::getTempLogFile, args);
           returnValue[0] = 0;
         }
         catch (Throwable t)
         {
-          if (tempLogFile.isEnabled())
-          {
-            logger.warn(LocalizableMessage.raw("Error launching GUI: "+t));
-            StringBuilder buf = new StringBuilder();
-            while (t != null)
-            {
-              for (StackTraceElement aStack : t.getStackTrace()) {
-                buf.append(aStack).append("\n");
-              }
-
-              t = t.getCause();
-              if (t != null)
-              {
-                buf.append("Root cause:\n");
-              }
-            }
-            logger.warn(LocalizableMessage.raw(buf));
-          }
+          // Kept rather than logged: a GUI which does not come up is not by itself an
+          // operation that failed, and creating a log here would leave one behind on every
+          // headless road that installs nothing (issue #1030). It goes into the log as soon
+          // as something asks for one.
+          guiLaunchFailure = t;
         }
       }
     });
@@ -240,6 +306,26 @@
   }
 
   /**
+   * Shows the splash screen and, behind it, builds the wizard.
+   * <p>
+   * The log file is handed over as a supplier and not as a file: the splash screen comes up
+   * before the user has said anything, and a log created there outlives every road that
+   * installs nothing - a quit at any wizard step, a server which is configured already
+   * (issue #1030). The application asks for it when it starts the operation.
+   * <p>
+   * Package-private so that a test can drive {@link #launchGui(String[])} without a display.
+   *
+   * @param tempLogFile
+   *          supplies the temporary log file of the application
+   * @param args
+   *          the arguments to pass to the splash screen
+   */
+  void startSplashScreen(final Supplier<TempLogFile> tempLogFile, final String[] args)
+  {
+    SplashScreen.main(tempLogFile, args);
+  }
+
+  /**
    * Gets the frame title of the GUI application that will be used
    * in some operating systems.
    * @return internationalized String representing the frame title
@@ -337,6 +423,9 @@
       }
       System.exit(ReturnCode.SUCCESSFUL.getReturnCode());
     } else if (isCli()) {
+      // An operation is about to run: from here on there is something worth logging, and
+      // preExit() names the file. The roads above attempt nothing and leave no log behind.
+      getTempLogFile();
       CliApplication cliApp = createCliApplication();
       int exitCode = launchCli(cliApp);
       preExit(cliApp);
@@ -345,6 +434,10 @@
       willLaunchGui();
       int exitCode = launchGui(args);
       if (exitCode != 0) {
+        // The GUI did not come up and the operation runs on the command line after all: from
+        // here on there is something worth logging, the reason the GUI failed included. The
+        // log comes first, so that guiLaunchFailed() can name the file holding that reason.
+        getTempLogFile();
         guiLaunchFailed();
         CliApplication cliApp = createCliApplication();
         exitCode = launchCli(cliApp);
@@ -361,8 +454,8 @@
 
         // Add an extra space systematically
         System.out.println();
-        if (tempLogFile.isEnabled()) {
-          System.out.println(INFO_GENERAL_SEE_FOR_DETAILS.get(tempLogFile.getPath()));
+        if (hasTempLogFile()) {
+          System.out.println(INFO_GENERAL_SEE_FOR_DETAILS.get(getTempLogFile().getPath()));
         }
       }
     }

--
Gitblit v1.10.0