From 8fd0d93574a561bdebaf738381b083914ec1b86e Mon Sep 17 00:00:00 2001 From: Tony Germano Date: Sun, 22 Feb 2026 02:18:41 -0500 Subject: [PATCH 1/5] rename LoginPanel to DefaultLoginPanel Signed-off-by: Tony Germano --- ...LoginPanel.java => DefaultLoginPanel.java} | 26 +++++++++---------- .../com/mirth/connect/client/ui/Frame.java | 8 +++--- .../com/mirth/connect/client/ui/Mirth.java | 6 ++--- 3 files changed, 20 insertions(+), 20 deletions(-) rename client/src/com/mirth/connect/client/ui/{LoginPanel.java => DefaultLoginPanel.java} (97%) diff --git a/client/src/com/mirth/connect/client/ui/LoginPanel.java b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java similarity index 97% rename from client/src/com/mirth/connect/client/ui/LoginPanel.java rename to client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java index 0414b7acc..ce9a51d39 100644 --- a/client/src/com/mirth/connect/client/ui/LoginPanel.java +++ b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java @@ -37,13 +37,13 @@ import com.mirth.connect.plugins.MultiFactorAuthenticationClientPlugin; import com.mirth.connect.util.MirthSSLUtil; -public class LoginPanel extends javax.swing.JFrame { +public class DefaultLoginPanel extends javax.swing.JFrame { private Client client; private static final String ERROR_MESSAGE = "There was an error connecting to the server at the specified address. Please verify that the server is up and running."; - private static LoginPanel instance = null; + private static DefaultLoginPanel instance = null; - private LoginPanel() { + private DefaultLoginPanel() { initComponents(); DisplayUtil.setResizable(this, false); jLabel2.setForeground(UIConstants.HEADER_TITLE_TEXT_COLOR); @@ -81,10 +81,10 @@ public void mouseClicked(java.awt.event.MouseEvent evt) { errorTextArea.setDisabledTextColor(Color.RED); } - public static LoginPanel getInstance() { - synchronized (LoginPanel.class) { + public static DefaultLoginPanel getInstance() { + synchronized (DefaultLoginPanel.class) { if (instance == null) { - instance = new LoginPanel(); + instance = new DefaultLoginPanel(); } return instance; } @@ -448,8 +448,8 @@ public Void doInBackground() { // If SUCCESS or SUCCESS_GRACE_PERIOD if (loginStatus != null && loginStatus.isSuccess()) { if (!handleSuccess(loginStatus)) { - LoginPanel.getInstance().setVisible(false); - LoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + DefaultLoginPanel.getInstance().setVisible(false); + DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } } else { // Assume failure unless overridden by a plugin @@ -462,13 +462,13 @@ public Void doInBackground() { String updatedUsername = StringUtils.defaultString(loginStatus.getUpdatedUsername(), username.getText()); MultiFactorAuthenticationClientPlugin plugin = (MultiFactorAuthenticationClientPlugin) Class.forName(extendedLoginStatus.getClientPluginClass()).newInstance(); - loginStatus = plugin.authenticate(LoginPanel.this, client, updatedUsername, loginStatus); + loginStatus = plugin.authenticate(DefaultLoginPanel.this, client, updatedUsername, loginStatus); if (loginStatus != null && loginStatus.isSuccess()) { errorOccurred = false; if (!handleSuccess(loginStatus)) { - LoginPanel.getInstance().setVisible(false); - LoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + DefaultLoginPanel.getInstance().setVisible(false); + DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } } } @@ -504,7 +504,7 @@ private boolean handleSuccess(LoginStatus loginStatus) throws ClientException { PublicServerSettings publicServerSettings = client.getPublicServerSettings(); if (publicServerSettings.getLoginNotificationEnabled() == true) { - CustomBannerPanelDialog customBannerPanelDialog = new CustomBannerPanelDialog(LoginPanel.getInstance(), "Login Notification", publicServerSettings.getLoginNotificationMessage()); + CustomBannerPanelDialog customBannerPanelDialog = new CustomBannerPanelDialog(DefaultLoginPanel.getInstance(), "Login Notification", publicServerSettings.getLoginNotificationMessage()); boolean isAccepted = customBannerPanelDialog.isAccepted(); if (isAccepted == true) { @@ -559,7 +559,7 @@ private boolean handleSuccess(LoginStatus loginStatus) throws ClientException { PlatformUI.USER_NAME = StringUtils.defaultString(loginStatus.getUpdatedUsername(), username.getText()); setStatus("Authenticated..."); new Mirth(client); - LoginPanel.getInstance().setVisible(false); + DefaultLoginPanel.getInstance().setVisible(false); User currentUser = PlatformUI.MIRTH_FRAME.getCurrentUser(PlatformUI.MIRTH_FRAME); Properties userPreferences = new Properties(); diff --git a/client/src/com/mirth/connect/client/ui/Frame.java b/client/src/com/mirth/connect/client/ui/Frame.java index 633a2791a..fc79d61a6 100644 --- a/client/src/com/mirth/connect/client/ui/Frame.java +++ b/client/src/com/mirth/connect/client/ui/Frame.java @@ -543,7 +543,7 @@ public void eventDispatched(AWTEvent e) */ public void setupFrame(Client mirthClient) throws ClientException { - LoginPanel login = LoginPanel.getInstance(); + DefaultLoginPanel login = DefaultLoginPanel.getInstance(); // Initialize the send message dialog editMessageDialog = new EditMessageDialog(); @@ -1524,7 +1524,7 @@ public void alertThrowable(Component parentComponent, Throwable t, String custom } mirthClient.close(); this.dispose(); - LoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); return; } else if (t.getCause() != null && t.getCause() instanceof HttpHostConnectException && (StringUtils.contains(t.getCause().getMessage(), "Connection refused") || StringUtils.contains(t.getCause().getMessage(), "Host is down"))) { connectionError = true; @@ -1542,7 +1542,7 @@ public void alertThrowable(Component parentComponent, Throwable t, String custom } mirthClient.close(); this.dispose(); - LoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); return; } } @@ -2292,7 +2292,7 @@ public boolean logout(boolean quit, boolean confirmFirst) { this.dispose(); if (!quit) { - LoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } return true; diff --git a/client/src/com/mirth/connect/client/ui/Mirth.java b/client/src/com/mirth/connect/client/ui/Mirth.java index eacb68331..2f5ee981d 100644 --- a/client/src/com/mirth/connect/client/ui/Mirth.java +++ b/client/src/com/mirth/connect/client/ui/Mirth.java @@ -56,7 +56,7 @@ public Mirth(Client mirthClient) throws ClientException { UIManager.put("Tree.closedIcon", UIConstants.CLOSED_ICON); userPreferences = Preferences.userNodeForPackage(Mirth.class); - LoginPanel.getInstance().setStatus("Loading components..."); + DefaultLoginPanel.getInstance().setStatus("Loading components..."); PlatformUI.MIRTH_FRAME.setupFrame(mirthClient); boolean maximized; @@ -116,7 +116,7 @@ public static void aboutMac() { * @return quit */ public static boolean quitMac() { - return (LoginPanel.getInstance().isVisible() || (PlatformUI.MIRTH_FRAME != null && PlatformUI.MIRTH_FRAME.logout(true))); + return (DefaultLoginPanel.getInstance().isVisible() || (PlatformUI.MIRTH_FRAME != null && PlatformUI.MIRTH_FRAME.logout(true))); } /** @@ -278,7 +278,7 @@ private static void start(final String server, final String version, final Strin public void run() { initUIManager(); PlatformUI.BACKGROUND_IMAGE = new ImageIcon(com.mirth.connect.client.ui.Frame.class.getResource("images/header_nologo.png")); - LoginPanel.getInstance().initialize(server, version, username, password); + DefaultLoginPanel.getInstance().initialize(server, version, username, password); } }); } From 62e5b119e8a2067f84be9019b9d4999b7c20eee9 Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Wed, 3 Sep 2025 09:50:26 -0500 Subject: [PATCH 2/5] Moved handleSuccess to Mirth.java - boots app, not related to panel Signed-off-by: Mitch Gaffigan --- .../connect/client/ui/DefaultLoginPanel.java | 152 +----------------- .../com/mirth/connect/client/ui/Mirth.java | 149 +++++++++++++++++ 2 files changed, 151 insertions(+), 150 deletions(-) diff --git a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java index ce9a51d39..014bd5548 100644 --- a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java +++ b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java @@ -4,38 +4,27 @@ package com.mirth.connect.client.ui; -import static com.mirth.connect.client.core.BrandingConstants.CHECK_FOR_NOTIFICATIONS; - import java.awt.Color; import java.awt.Cursor; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Set; -import java.util.prefs.Preferences; import javax.swing.ImageIcon; import javax.swing.SwingWorker; -import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import com.mirth.connect.client.core.Client; import com.mirth.connect.client.core.ClientException; -import com.mirth.connect.client.core.ConnectServiceUtil; import com.mirth.connect.client.core.UnauthorizedException; import com.mirth.connect.client.core.api.servlets.UserServletInterface; import com.mirth.connect.client.ui.util.DisplayUtil; import com.mirth.connect.model.ExtendedLoginStatus; import com.mirth.connect.model.LoginStatus; -import com.mirth.connect.model.PublicServerSettings; -import com.mirth.connect.model.User; -import com.mirth.connect.model.converters.ObjectXMLSerializer; import com.mirth.connect.plugins.MultiFactorAuthenticationClientPlugin; -import com.mirth.connect.util.MirthSSLUtil; public class DefaultLoginPanel extends javax.swing.JFrame { @@ -97,8 +86,6 @@ public void initialize(String mirthServer, String version, String user, String p return; } - PlatformUI.CLIENT_VERSION = version; - setTitle(String.format("%s %s - Login", BrandingConstants.PRODUCT_NAME, version)); setIconImage(BrandingConstants.FAVICON.getImage()); @@ -447,7 +434,7 @@ public Void doInBackground() { // If SUCCESS or SUCCESS_GRACE_PERIOD if (loginStatus != null && loginStatus.isSuccess()) { - if (!handleSuccess(loginStatus)) { + if (!Mirth.handleLoginSuccess(client, loginStatus, username.getText())) { DefaultLoginPanel.getInstance().setVisible(false); DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } @@ -466,7 +453,7 @@ public Void doInBackground() { if (loginStatus != null && loginStatus.isSuccess()) { errorOccurred = false; - if (!handleSuccess(loginStatus)) { + if (!Mirth.handleLoginSuccess(client, loginStatus, username.getText())) { DefaultLoginPanel.getInstance().setVisible(false); DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } @@ -499,141 +486,6 @@ public Void doInBackground() { return null; } - private boolean handleSuccess(LoginStatus loginStatus) throws ClientException { - try { - PublicServerSettings publicServerSettings = client.getPublicServerSettings(); - - if (publicServerSettings.getLoginNotificationEnabled() == true) { - CustomBannerPanelDialog customBannerPanelDialog = new CustomBannerPanelDialog(DefaultLoginPanel.getInstance(), "Login Notification", publicServerSettings.getLoginNotificationMessage()); - boolean isAccepted = customBannerPanelDialog.isAccepted(); - - if (isAccepted == true) { - client.setUserNotificationAcknowledged(client.getCurrentUser().getId()); - } - else { - return false; - } - } - - String environmentName = publicServerSettings.getEnvironmentName(); - if (!StringUtils.isBlank(environmentName)) { - PlatformUI.ENVIRONMENT_NAME = environmentName; - } - - String serverName = publicServerSettings.getServerName(); - if (!StringUtils.isBlank(serverName)) { - PlatformUI.SERVER_NAME = serverName; - } else { - PlatformUI.SERVER_NAME = null; - } - - Color defaultBackgroundColor = publicServerSettings.getDefaultAdministratorBackgroundColor(); - if (defaultBackgroundColor != null) { - PlatformUI.DEFAULT_BACKGROUND_COLOR = defaultBackgroundColor; - } - } catch (ClientException e) { - PlatformUI.SERVER_NAME = null; - } - - try { - String database = (String) client.getAbout().get("database"); - if (!StringUtils.isBlank(database)) { - PlatformUI.SERVER_DATABASE = database; - } else { - PlatformUI.SERVER_DATABASE = null; - } - } catch (ClientException e) { - PlatformUI.SERVER_DATABASE = null; - } - - try { - Map map = client.getProtocolsAndCipherSuites(); - PlatformUI.SERVER_HTTPS_SUPPORTED_PROTOCOLS = map.get(MirthSSLUtil.KEY_SUPPORTED_PROTOCOLS); - PlatformUI.SERVER_HTTPS_ENABLED_CLIENT_PROTOCOLS = map.get(MirthSSLUtil.KEY_ENABLED_CLIENT_PROTOCOLS); - PlatformUI.SERVER_HTTPS_ENABLED_SERVER_PROTOCOLS = map.get(MirthSSLUtil.KEY_ENABLED_SERVER_PROTOCOLS); - PlatformUI.SERVER_HTTPS_SUPPORTED_CIPHER_SUITES = map.get(MirthSSLUtil.KEY_SUPPORTED_CIPHER_SUITES); - PlatformUI.SERVER_HTTPS_ENABLED_CIPHER_SUITES = map.get(MirthSSLUtil.KEY_ENABLED_CIPHER_SUITES); - } catch (ClientException e) { - } - - PlatformUI.USER_NAME = StringUtils.defaultString(loginStatus.getUpdatedUsername(), username.getText()); - setStatus("Authenticated..."); - new Mirth(client); - DefaultLoginPanel.getInstance().setVisible(false); - - User currentUser = PlatformUI.MIRTH_FRAME.getCurrentUser(PlatformUI.MIRTH_FRAME); - Properties userPreferences = new Properties(); - Set preferenceNames = new HashSet(); - preferenceNames.add("firstlogin"); - preferenceNames.add("checkForNotifications"); - preferenceNames.add("showNotificationPopup"); - preferenceNames.add("archivedNotifications"); - try { - userPreferences = client.getUserPreferences(currentUser.getId(), preferenceNames); - - // Display registration dialog if it's the user's first time logging in - String firstlogin = userPreferences.getProperty("firstlogin"); - if (firstlogin == null || BooleanUtils.toBoolean(firstlogin)) { - if (Integer.valueOf(currentUser.getId()) == 1) { - // if current user is user 1: - // 1. check system preferences for user information - // 2. if system preferences exist, populate screen using currentUser - Preferences preferences = Preferences.userNodeForPackage(Mirth.class); - String systemUserInfo = preferences.get("userLoginInfo", null); - if (systemUserInfo != null) { - String info[] = systemUserInfo.split(",", 0); - currentUser.setUsername(info[0]); - currentUser.setFirstName(info[1]); - currentUser.setLastName(info[2]); - currentUser.setEmail(info[3]); - currentUser.setCountry(info[4]); - currentUser.setStateTerritory(info[5]); - currentUser.setPhoneNumber(info[6]); - currentUser.setOrganization(info[7]); - currentUser.setRole(info[8]); - currentUser.setIndustry(info[9]); - currentUser.setDescription(info[10]); - } - } - FirstLoginDialog firstLoginDialog = new FirstLoginDialog(currentUser); - // if leaving the first login dialog without saving - if (!firstLoginDialog.getResult()) { - return false; - } - } else if (loginStatus.getStatus() == LoginStatus.Status.SUCCESS_GRACE_PERIOD) { - new ChangePasswordDialog(currentUser, loginStatus.getMessage()); - } - - // Check for new notifications from update server if enabled - String checkForNotifications = userPreferences.getProperty("checkForNotifications"); - if (CHECK_FOR_NOTIFICATIONS - && (checkForNotifications == null || BooleanUtils.toBoolean(checkForNotifications))) { - Set archivedNotifications = new HashSet(); - String archivedNotificationString = userPreferences.getProperty("archivedNotifications"); - if (archivedNotificationString != null) { - archivedNotifications = ObjectXMLSerializer.getInstance().deserialize(archivedNotificationString, Set.class); - } - // Update the Other Tasks pane with the unarchived notification count - int unarchivedNotifications = ConnectServiceUtil.getNotificationCount(PlatformUI.SERVER_ID, PlatformUI.SERVER_VERSION, LoadedExtensions.getInstance().getExtensionVersions(), archivedNotifications, PlatformUI.HTTPS_PROTOCOLS, PlatformUI.HTTPS_CIPHER_SUITES); - PlatformUI.MIRTH_FRAME.updateNotificationTaskName(unarchivedNotifications); - - // Display notification dialog if enabled and if there are new notifications - String showNotificationPopup = userPreferences.getProperty("showNotificationPopup"); - if (showNotificationPopup == null || BooleanUtils.toBoolean(showNotificationPopup)) { - if (unarchivedNotifications > 0) { - new NotificationDialog(); - } - } - } - } catch (ClientException e) { - PlatformUI.MIRTH_FRAME.alertThrowable(PlatformUI.MIRTH_FRAME, e); - } - - PlatformUI.MIRTH_FRAME.sendUsageStatistics(); - - return true; - } - public void done() {} }; worker.execute(); diff --git a/client/src/com/mirth/connect/client/ui/Mirth.java b/client/src/com/mirth/connect/client/ui/Mirth.java index 2f5ee981d..2b27371b8 100644 --- a/client/src/com/mirth/connect/client/ui/Mirth.java +++ b/client/src/com/mirth/connect/client/ui/Mirth.java @@ -3,12 +3,18 @@ package com.mirth.connect.client.ui; +import static com.mirth.connect.client.core.BrandingConstants.CHECK_FOR_NOTIFICATIONS; + import java.awt.Color; import java.awt.Toolkit; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.IOException; import java.util.prefs.Preferences; +import java.util.Map; +import java.util.Set; +import java.util.Properties; +import java.util.HashSet; import javax.imageio.ImageIO; import javax.swing.ImageIcon; @@ -22,6 +28,7 @@ import javax.swing.text.JTextComponent; import javax.swing.text.JTextComponent.KeyBinding; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.apache.logging.log4j.Level; @@ -36,6 +43,12 @@ import com.jgoodies.looks.plastic.PlasticXPLookAndFeel; import com.mirth.connect.client.core.Client; import com.mirth.connect.client.core.ClientException; +import com.mirth.connect.client.core.ConnectServiceUtil; +import com.mirth.connect.model.LoginStatus; +import com.mirth.connect.model.PublicServerSettings; +import com.mirth.connect.model.User; +import com.mirth.connect.model.converters.ObjectXMLSerializer; +import com.mirth.connect.util.MirthSSLUtil; /** * The main mirth class. Sets up the login and then authenticates the login information and sets up @@ -282,4 +295,140 @@ public void run() { } }); } + + public static boolean handleLoginSuccess(Client client, LoginStatus loginStatus, String userName) throws ClientException { + AbstractLoginPanel loginPanel = LoginPanelFactory.getInstance(); + try { + PublicServerSettings publicServerSettings = client.getPublicServerSettings(); + + if (publicServerSettings.getLoginNotificationEnabled() == true) { + CustomBannerPanelDialog customBannerPanelDialog = new CustomBannerPanelDialog(loginPanel, "Login Notification", publicServerSettings.getLoginNotificationMessage()); + boolean isAccepted = customBannerPanelDialog.isAccepted(); + + if (isAccepted == true) { + client.setUserNotificationAcknowledged(client.getCurrentUser().getId()); + } + else { + return false; + } + } + + String environmentName = publicServerSettings.getEnvironmentName(); + if (!StringUtils.isBlank(environmentName)) { + PlatformUI.ENVIRONMENT_NAME = environmentName; + } + + String serverName = publicServerSettings.getServerName(); + if (!StringUtils.isBlank(serverName)) { + PlatformUI.SERVER_NAME = serverName; + } else { + PlatformUI.SERVER_NAME = null; + } + + Color defaultBackgroundColor = publicServerSettings.getDefaultAdministratorBackgroundColor(); + if (defaultBackgroundColor != null) { + PlatformUI.DEFAULT_BACKGROUND_COLOR = defaultBackgroundColor; + } + } catch (ClientException e) { + PlatformUI.SERVER_NAME = null; + } + + try { + String database = (String) client.getAbout().get("database"); + if (!StringUtils.isBlank(database)) { + PlatformUI.SERVER_DATABASE = database; + } else { + PlatformUI.SERVER_DATABASE = null; + } + } catch (ClientException e) { + PlatformUI.SERVER_DATABASE = null; + } + + try { + Map map = client.getProtocolsAndCipherSuites(); + PlatformUI.SERVER_HTTPS_SUPPORTED_PROTOCOLS = map.get(MirthSSLUtil.KEY_SUPPORTED_PROTOCOLS); + PlatformUI.SERVER_HTTPS_ENABLED_CLIENT_PROTOCOLS = map.get(MirthSSLUtil.KEY_ENABLED_CLIENT_PROTOCOLS); + PlatformUI.SERVER_HTTPS_ENABLED_SERVER_PROTOCOLS = map.get(MirthSSLUtil.KEY_ENABLED_SERVER_PROTOCOLS); + PlatformUI.SERVER_HTTPS_SUPPORTED_CIPHER_SUITES = map.get(MirthSSLUtil.KEY_SUPPORTED_CIPHER_SUITES); + PlatformUI.SERVER_HTTPS_ENABLED_CIPHER_SUITES = map.get(MirthSSLUtil.KEY_ENABLED_CIPHER_SUITES); + } catch (ClientException e) { + } + + PlatformUI.USER_NAME = StringUtils.defaultString(loginStatus.getUpdatedUsername(), userName); + loginPanel.setStatus("Authenticated..."); + new Mirth(client); + loginPanel.setVisible(false); + + User currentUser = PlatformUI.MIRTH_FRAME.getCurrentUser(PlatformUI.MIRTH_FRAME); + Properties userPreferences = new Properties(); + Set preferenceNames = new HashSet(); + preferenceNames.add("firstlogin"); + preferenceNames.add("checkForNotifications"); + preferenceNames.add("showNotificationPopup"); + preferenceNames.add("archivedNotifications"); + try { + userPreferences = client.getUserPreferences(currentUser.getId(), preferenceNames); + + // Display registration dialog if it's the user's first time logging in + String firstlogin = userPreferences.getProperty("firstlogin"); + if (firstlogin == null || BooleanUtils.toBoolean(firstlogin)) { + if (Integer.valueOf(currentUser.getId()) == 1) { + // if current user is user 1: + // 1. check system preferences for user information + // 2. if system preferences exist, populate screen using currentUser + Preferences preferences = Preferences.userNodeForPackage(Mirth.class); + String systemUserInfo = preferences.get("userLoginInfo", null); + if (systemUserInfo != null) { + String info[] = systemUserInfo.split(",", 0); + currentUser.setUsername(info[0]); + currentUser.setFirstName(info[1]); + currentUser.setLastName(info[2]); + currentUser.setEmail(info[3]); + currentUser.setCountry(info[4]); + currentUser.setStateTerritory(info[5]); + currentUser.setPhoneNumber(info[6]); + currentUser.setOrganization(info[7]); + currentUser.setRole(info[8]); + currentUser.setIndustry(info[9]); + currentUser.setDescription(info[10]); + } + } + FirstLoginDialog firstLoginDialog = new FirstLoginDialog(currentUser); + // if leaving the first login dialog without saving + if (!firstLoginDialog.getResult()) { + return false; + } + } else if (loginStatus.getStatus() == LoginStatus.Status.SUCCESS_GRACE_PERIOD) { + new ChangePasswordDialog(currentUser, loginStatus.getMessage()); + } + + // Check for new notifications from update server if enabled + String checkForNotifications = userPreferences.getProperty("checkForNotifications"); + if (CHECK_FOR_NOTIFICATIONS + && (checkForNotifications == null || BooleanUtils.toBoolean(checkForNotifications))) { + Set archivedNotifications = new HashSet(); + String archivedNotificationString = userPreferences.getProperty("archivedNotifications"); + if (archivedNotificationString != null) { + archivedNotifications = ObjectXMLSerializer.getInstance().deserialize(archivedNotificationString, Set.class); + } + // Update the Other Tasks pane with the unarchived notification count + int unarchivedNotifications = ConnectServiceUtil.getNotificationCount(PlatformUI.SERVER_ID, PlatformUI.SERVER_VERSION, LoadedExtensions.getInstance().getExtensionVersions(), archivedNotifications, PlatformUI.HTTPS_PROTOCOLS, PlatformUI.HTTPS_CIPHER_SUITES); + PlatformUI.MIRTH_FRAME.updateNotificationTaskName(unarchivedNotifications); + + // Display notification dialog if enabled and if there are new notifications + String showNotificationPopup = userPreferences.getProperty("showNotificationPopup"); + if (showNotificationPopup == null || BooleanUtils.toBoolean(showNotificationPopup)) { + if (unarchivedNotifications > 0) { + new NotificationDialog(); + } + } + } + } catch (ClientException e) { + PlatformUI.MIRTH_FRAME.alertThrowable(PlatformUI.MIRTH_FRAME, e); + } + + PlatformUI.MIRTH_FRAME.sendUsageStatistics(); + + return true; + } } From 33bc20d2b895ce10ab5c1ffe3d3724aef5b7165e Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Wed, 3 Sep 2025 09:53:28 -0500 Subject: [PATCH 3/5] Removed duplicated error handling logic to separate method, avoid relying on global state Signed-off-by: Mitch Gaffigan --- .../connect/client/ui/DefaultLoginPanel.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java index 014bd5548..410deaac9 100644 --- a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java +++ b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java @@ -28,7 +28,6 @@ public class DefaultLoginPanel extends javax.swing.JFrame { - private Client client; private static final String ERROR_MESSAGE = "There was an error connecting to the server at the specified address. Please verify that the server is up and running."; private static DefaultLoginPanel instance = null; @@ -402,6 +401,7 @@ private void passwordActionPerformed(java.awt.event.ActionEvent evt)// GEN-FIRST private void loginButtonActionPerformed(java.awt.event.ActionEvent evt)// GEN-FIRST:event_loginButtonActionPerformed {// GEN-HEADEREND:event_loginButtonActionPerformed errorPane.setVisible(false); + LoginPanel loginPanel = this; SwingWorker worker = new SwingWorker() { @@ -410,7 +410,7 @@ public Void doInBackground() { try { String server = serverName.getText(); - client = new Client(server, PlatformUI.HTTPS_PROTOCOLS, PlatformUI.HTTPS_CIPHER_SUITES); + Client client = new Client(server, PlatformUI.HTTPS_PROTOCOLS, PlatformUI.HTTPS_CIPHER_SUITES); PlatformUI.SERVER_URL = server; // Attempt to login @@ -435,8 +435,8 @@ public Void doInBackground() { // If SUCCESS or SUCCESS_GRACE_PERIOD if (loginStatus != null && loginStatus.isSuccess()) { if (!Mirth.handleLoginSuccess(client, loginStatus, username.getText())) { - DefaultLoginPanel.getInstance().setVisible(false); - DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + loginPanel.setVisible(false); + loginPanel.initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } } else { // Assume failure unless overridden by a plugin @@ -454,8 +454,8 @@ public Void doInBackground() { if (loginStatus != null && loginStatus.isSuccess()) { errorOccurred = false; if (!Mirth.handleLoginSuccess(client, loginStatus, username.getText())) { - DefaultLoginPanel.getInstance().setVisible(false); - DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + loginPanel.setVisible(false); + loginPanel.initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } } } @@ -464,25 +464,16 @@ public Void doInBackground() { if (errorOccurred) { if (loginStatus != null) { - errorTextArea.setText(loginStatus.getMessage()); + setError(loginStatus.getMessage()); } else { - errorTextArea.setText(ERROR_MESSAGE); + setError(ERROR_MESSAGE); } } } catch (Throwable t) { - errorOccurred = true; - errorTextArea.setText(ERROR_MESSAGE); + setError(ERROR_MESSAGE); t.printStackTrace(); } - if (errorOccurred) { - errorPane.setVisible(true); - loggingIn.setVisible(false); - loginMain.setVisible(true); - loginProgress.setIndeterminate(false); - password.grabFocus(); - } - return null; } @@ -509,6 +500,15 @@ public void setStatus(String status) { this.status.setText("Please wait: " + status); } + public void setError(String status) { + errorTextArea.setText(status); + errorPane.setVisible(true); + loggingIn.setVisible(false); + loginMain.setVisible(true); + loginProgress.setIndeterminate(false); + password.grabFocus(); + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton closeButton; private javax.swing.JScrollPane errorPane; From 14dc6d93edb063777c7e27d9e15af7f665e715c5 Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Wed, 3 Sep 2025 09:55:00 -0500 Subject: [PATCH 4/5] Remove LoginPanel singleton to separate class to allow other implementations Signed-off-by: Mitch Gaffigan --- .../connect/client/ui/AbstractLoginPanel.java | 17 ++++++++++++++ .../connect/client/ui/DefaultLoginPanel.java | 16 ++++--------- .../com/mirth/connect/client/ui/Frame.java | 8 +++---- .../connect/client/ui/LoginPanelFactory.java | 23 +++++++++++++++++++ .../com/mirth/connect/client/ui/Mirth.java | 6 ++--- 5 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java create mode 100644 client/src/com/mirth/connect/client/ui/LoginPanelFactory.java diff --git a/client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java b/client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java new file mode 100644 index 000000000..0670d81ec --- /dev/null +++ b/client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java @@ -0,0 +1,17 @@ +package com.mirth.connect.client.ui; + +/** + * Public interface for the login panel so alternative implementations can be provided. + */ +public abstract class AbstractLoginPanel extends javax.swing.JFrame { + + /** + * Initialize and show the login UI. + */ + public abstract void initialize(String mirthServer, String version, String user, String pass); + + /** + * Update the status text shown on the login UI. + */ + public abstract void setStatus(String status); +} diff --git a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java index 410deaac9..871b07cc6 100644 --- a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java +++ b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java @@ -26,12 +26,11 @@ import com.mirth.connect.model.LoginStatus; import com.mirth.connect.plugins.MultiFactorAuthenticationClientPlugin; -public class DefaultLoginPanel extends javax.swing.JFrame { +public class DefaultLoginPanel extends AbstractLoginPanel { private static final String ERROR_MESSAGE = "There was an error connecting to the server at the specified address. Please verify that the server is up and running."; - private static DefaultLoginPanel instance = null; - private DefaultLoginPanel() { + public DefaultLoginPanel() { initComponents(); DisplayUtil.setResizable(this, false); jLabel2.setForeground(UIConstants.HEADER_TITLE_TEXT_COLOR); @@ -69,15 +68,7 @@ public void mouseClicked(java.awt.event.MouseEvent evt) { errorTextArea.setDisabledTextColor(Color.RED); } - public static DefaultLoginPanel getInstance() { - synchronized (DefaultLoginPanel.class) { - if (instance == null) { - instance = new DefaultLoginPanel(); - } - return instance; - } - } - + @Override public void initialize(String mirthServer, String version, String user, String pass) { synchronized (this) { // Do not initialize another login window if one is already visible @@ -496,6 +487,7 @@ private void closeButtonActionPerformed(java.awt.event.ActionEvent evt)// GEN-FI System.exit(0); }// GEN-LAST:event_closeButtonActionPerformed + @Override public void setStatus(String status) { this.status.setText("Please wait: " + status); } diff --git a/client/src/com/mirth/connect/client/ui/Frame.java b/client/src/com/mirth/connect/client/ui/Frame.java index fc79d61a6..8a366eac0 100644 --- a/client/src/com/mirth/connect/client/ui/Frame.java +++ b/client/src/com/mirth/connect/client/ui/Frame.java @@ -543,7 +543,7 @@ public void eventDispatched(AWTEvent e) */ public void setupFrame(Client mirthClient) throws ClientException { - DefaultLoginPanel login = DefaultLoginPanel.getInstance(); + AbstractLoginPanel login = LoginPanelFactory.getInstance(); // Initialize the send message dialog editMessageDialog = new EditMessageDialog(); @@ -1524,7 +1524,7 @@ public void alertThrowable(Component parentComponent, Throwable t, String custom } mirthClient.close(); this.dispose(); - DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + LoginPanelFactory.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); return; } else if (t.getCause() != null && t.getCause() instanceof HttpHostConnectException && (StringUtils.contains(t.getCause().getMessage(), "Connection refused") || StringUtils.contains(t.getCause().getMessage(), "Host is down"))) { connectionError = true; @@ -1542,7 +1542,7 @@ public void alertThrowable(Component parentComponent, Throwable t, String custom } mirthClient.close(); this.dispose(); - DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + LoginPanelFactory.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); return; } } @@ -2292,7 +2292,7 @@ public boolean logout(boolean quit, boolean confirmFirst) { this.dispose(); if (!quit) { - DefaultLoginPanel.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + LoginPanelFactory.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); } return true; diff --git a/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java b/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java new file mode 100644 index 000000000..9601ad0d1 --- /dev/null +++ b/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java @@ -0,0 +1,23 @@ +package com.mirth.connect.client.ui; + +/** + * Factory for obtaining the application's LoginPanel implementation. + */ +public class LoginPanelFactory { + + private static AbstractLoginPanel provider = null; + + public static synchronized AbstractLoginPanel getInstance() { + if (provider == null) { + provider = new DefaultLoginPanel(); + } + return provider; + } + + /** + * Replace the current provider. This is used to switch between login implementations at runtime. + */ + public static synchronized void setProvider(AbstractLoginPanel newProvider) { + provider = newProvider; + } +} diff --git a/client/src/com/mirth/connect/client/ui/Mirth.java b/client/src/com/mirth/connect/client/ui/Mirth.java index 2b27371b8..76206ec61 100644 --- a/client/src/com/mirth/connect/client/ui/Mirth.java +++ b/client/src/com/mirth/connect/client/ui/Mirth.java @@ -69,7 +69,7 @@ public Mirth(Client mirthClient) throws ClientException { UIManager.put("Tree.closedIcon", UIConstants.CLOSED_ICON); userPreferences = Preferences.userNodeForPackage(Mirth.class); - DefaultLoginPanel.getInstance().setStatus("Loading components..."); + LoginPanelFactory.getInstance().setStatus("Loading components..."); PlatformUI.MIRTH_FRAME.setupFrame(mirthClient); boolean maximized; @@ -129,7 +129,7 @@ public static void aboutMac() { * @return quit */ public static boolean quitMac() { - return (DefaultLoginPanel.getInstance().isVisible() || (PlatformUI.MIRTH_FRAME != null && PlatformUI.MIRTH_FRAME.logout(true))); + return (LoginPanelFactory.getInstance().isVisible() || (PlatformUI.MIRTH_FRAME != null && PlatformUI.MIRTH_FRAME.logout(true))); } /** @@ -291,7 +291,7 @@ private static void start(final String server, final String version, final Strin public void run() { initUIManager(); PlatformUI.BACKGROUND_IMAGE = new ImageIcon(com.mirth.connect.client.ui.Frame.class.getResource("images/header_nologo.png")); - DefaultLoginPanel.getInstance().initialize(server, version, username, password); + LoginPanelFactory.getInstance().initialize(server, version, username, password); } }); } From c36876873e89edefe4214fba25efa06c5e6e6d95 Mon Sep 17 00:00:00 2001 From: Tony Germano Date: Thu, 26 Feb 2026 08:20:03 +0000 Subject: [PATCH 5/5] Extract LoginPanel interface with SPI discovery - Create LoginPanel interface with LoginSuccessHandler callback, 5-parameter initialize, setStatus, setVisible, isVisible, and showLoginNotification methods - Update DefaultLoginPanel: extends JFrame implements LoginPanel, package-private constructor, store onSuccess callback, add showLoginNotification with proper EDT marshalling via invokeAndWait - Rewrite LoginPanelFactory with ServiceLoader (SPI) discovery and getLoginPanel() API, falling back to DefaultLoginPanel - Add Mirth.showLogin() as single entry point for login initiation, passing Mirth::handleLoginSuccess as the callback - Make Mirth.handleLoginSuccess private, replace CustomBannerPanelDialog with loginPanel.showLoginNotification() delegation - Decouple Frame from login panel: setupFrame accepts Consumer statusCallback, re-login sites use Mirth.showLogin() - Delete AbstractLoginPanel.java --- .../connect/client/ui/AbstractLoginPanel.java | 17 -- ...LoginPanel.form => DefaultLoginPanel.form} | 0 .../connect/client/ui/DefaultLoginPanel.java | 80 ++++++++-- .../com/mirth/connect/client/ui/Frame.java | 36 ++--- .../mirth/connect/client/ui/LoginPanel.java | 149 ++++++++++++++++++ .../connect/client/ui/LoginPanelFactory.java | 39 +++-- .../com/mirth/connect/client/ui/Mirth.java | 28 ++-- 7 files changed, 273 insertions(+), 76 deletions(-) delete mode 100644 client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java rename client/src/com/mirth/connect/client/ui/{LoginPanel.form => DefaultLoginPanel.form} (100%) create mode 100644 client/src/com/mirth/connect/client/ui/LoginPanel.java diff --git a/client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java b/client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java deleted file mode 100644 index 0670d81ec..000000000 --- a/client/src/com/mirth/connect/client/ui/AbstractLoginPanel.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.mirth.connect.client.ui; - -/** - * Public interface for the login panel so alternative implementations can be provided. - */ -public abstract class AbstractLoginPanel extends javax.swing.JFrame { - - /** - * Initialize and show the login UI. - */ - public abstract void initialize(String mirthServer, String version, String user, String pass); - - /** - * Update the status text shown on the login UI. - */ - public abstract void setStatus(String status); -} diff --git a/client/src/com/mirth/connect/client/ui/LoginPanel.form b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.form similarity index 100% rename from client/src/com/mirth/connect/client/ui/LoginPanel.form rename to client/src/com/mirth/connect/client/ui/DefaultLoginPanel.form diff --git a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java index 871b07cc6..98c8905d2 100644 --- a/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java +++ b/client/src/com/mirth/connect/client/ui/DefaultLoginPanel.java @@ -6,16 +6,20 @@ import java.awt.Color; import java.awt.Cursor; +import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.ImageIcon; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import com.mirth.connect.client.core.Client; import com.mirth.connect.client.core.ClientException; @@ -26,11 +30,14 @@ import com.mirth.connect.model.LoginStatus; import com.mirth.connect.plugins.MultiFactorAuthenticationClientPlugin; -public class DefaultLoginPanel extends AbstractLoginPanel { +class DefaultLoginPanel extends javax.swing.JFrame implements LoginPanel { + private static final Logger logger = LogManager.getLogger(DefaultLoginPanel.class); private static final String ERROR_MESSAGE = "There was an error connecting to the server at the specified address. Please verify that the server is up and running."; - public DefaultLoginPanel() { + private LoginSuccessHandler onSuccess; + + DefaultLoginPanel() { initComponents(); DisplayUtil.setResizable(this, false); jLabel2.setForeground(UIConstants.HEADER_TITLE_TEXT_COLOR); @@ -69,12 +76,21 @@ public void mouseClicked(java.awt.event.MouseEvent evt) { } @Override - public void initialize(String mirthServer, String version, String user, String pass) { + public void initialize(String mirthServer, String version, String user, String pass, LoginSuccessHandler onSuccess) { + if (SwingUtilities.isEventDispatchThread()) { + doInitialize(mirthServer, version, user, pass, onSuccess); + } else { + SwingUtilities.invokeLater(() -> doInitialize(mirthServer, version, user, pass, onSuccess)); + } + } + + private void doInitialize(String mirthServer, String version, String user, String pass, LoginSuccessHandler onSuccess) { synchronized (this) { // Do not initialize another login window if one is already visible if (isVisible()) { return; } + this.onSuccess = onSuccess; setTitle(String.format("%s %s - Login", BrandingConstants.PRODUCT_NAME, version)); setIconImage(BrandingConstants.FAVICON.getImage()); @@ -392,7 +408,7 @@ private void passwordActionPerformed(java.awt.event.ActionEvent evt)// GEN-FIRST private void loginButtonActionPerformed(java.awt.event.ActionEvent evt)// GEN-FIRST:event_loginButtonActionPerformed {// GEN-HEADEREND:event_loginButtonActionPerformed errorPane.setVisible(false); - LoginPanel loginPanel = this; + DefaultLoginPanel loginPanel = this; SwingWorker worker = new SwingWorker() { @@ -425,9 +441,9 @@ public Void doInBackground() { // If SUCCESS or SUCCESS_GRACE_PERIOD if (loginStatus != null && loginStatus.isSuccess()) { - if (!Mirth.handleLoginSuccess(client, loginStatus, username.getText())) { + if (!onSuccess.handle(client, loginStatus, username.getText())) { loginPanel.setVisible(false); - loginPanel.initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + loginPanel.initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", "", onSuccess); } } else { // Assume failure unless overridden by a plugin @@ -444,9 +460,9 @@ public Void doInBackground() { if (loginStatus != null && loginStatus.isSuccess()) { errorOccurred = false; - if (!Mirth.handleLoginSuccess(client, loginStatus, username.getText())) { + if (!onSuccess.handle(client, loginStatus, username.getText())) { loginPanel.setVisible(false); - loginPanel.initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + loginPanel.initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", "", onSuccess); } } } @@ -487,18 +503,48 @@ private void closeButtonActionPerformed(java.awt.event.ActionEvent evt)// GEN-FI System.exit(0); }// GEN-LAST:event_closeButtonActionPerformed + @Override + public void setVisible(boolean visible) { + if (SwingUtilities.isEventDispatchThread()) { + super.setVisible(visible); + } else { + SwingUtilities.invokeLater(() -> super.setVisible(visible)); + } + } + @Override public void setStatus(String status) { - this.status.setText("Please wait: " + status); + SwingUtilities.invokeLater(() -> this.status.setText("Please wait: " + status)); } - public void setError(String status) { - errorTextArea.setText(status); - errorPane.setVisible(true); - loggingIn.setVisible(false); - loginMain.setVisible(true); - loginProgress.setIndeterminate(false); - password.grabFocus(); + private void setError(String status) { + SwingUtilities.invokeLater(() -> { + errorTextArea.setText(status); + errorPane.setVisible(true); + loggingIn.setVisible(false); + loginMain.setVisible(true); + loginProgress.setIndeterminate(false); + password.grabFocus(); + }); + } + + @Override + public boolean showLoginNotification(String title, String message) { + // Called from a background thread (SwingWorker.doInBackground via handleLoginSuccess). + // Swing dialogs must be created and shown on the EDT to avoid deadlocks. + final AtomicBoolean accepted = new AtomicBoolean(false); + try { + SwingUtilities.invokeAndWait(() -> { + CustomBannerPanelDialog dialog = new CustomBannerPanelDialog(this, title, message); + accepted.set(dialog.isAccepted()); + }); + } catch (InterruptedException e) { + logger.warn("Login notification dialog interrupted; treating as declined"); + Thread.currentThread().interrupt(); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + return accepted.get(); } // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/client/src/com/mirth/connect/client/ui/Frame.java b/client/src/com/mirth/connect/client/ui/Frame.java index 8a366eac0..e5c85669c 100644 --- a/client/src/com/mirth/connect/client/ui/Frame.java +++ b/client/src/com/mirth/connect/client/ui/Frame.java @@ -1,11 +1,6 @@ -/* - * Copyright (c) Mirth Corporation. All rights reserved. - * - * http://www.mirthcorp.com - * - * The software in this package is published under the terms of the MPL license a copy of which has - * been included with this distribution in the LICENSE.txt file. - */ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: Mirth Corporation +// SPDX-FileCopyrightText: 2025-2026 Open Integration Engine Contributors package com.mirth.connect.client.ui; @@ -57,6 +52,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.function.Consumer; import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -541,15 +537,13 @@ public void eventDispatched(AWTEvent e) /** * Called to set up this main window frame. */ - public void setupFrame(Client mirthClient) throws ClientException { - - AbstractLoginPanel login = LoginPanelFactory.getInstance(); + public void setupFrame(Client mirthClient, Consumer statusCallback) throws ClientException { // Initialize the send message dialog editMessageDialog = new EditMessageDialog(); this.mirthClient = mirthClient; - login.setStatus("Loading extensions..."); + statusCallback.accept("Loading extensions..."); try { loadExtensionMetaData(); } catch (ClientException e) { @@ -589,10 +583,10 @@ public void setupFrame(Client mirthClient) throws ClientException { } setInitialVisibleTasks(); - login.setStatus("Loading preferences..."); + statusCallback.accept("Loading preferences..."); userPreferences = Preferences.userNodeForPackage(Mirth.class); userPreferences.put("defaultServer", PlatformUI.SERVER_URL); - login.setStatus("Loading GUI components..."); + statusCallback.accept("Loading GUI components..."); splitPane.setDividerSize(0); splitPane.setBorder(BorderFactory.createEmptyBorder()); @@ -661,15 +655,15 @@ public void setupFrame(Client mirthClient) throws ClientException { } setCurrentTaskPaneContainer(taskPaneContainer); - login.setStatus("Loading dashboard..."); + statusCallback.accept("Loading dashboard..."); doShowDashboard(); - login.setStatus("Loading channel editor..."); + statusCallback.accept("Loading channel editor..."); channelEditPanel = new ChannelSetup(); - login.setStatus("Loading alert editor..."); + statusCallback.accept("Loading alert editor..."); if (alertEditPanel == null) { alertEditPanel = new DefaultAlertEditPanel(); } - login.setStatus("Loading message browser..."); + statusCallback.accept("Loading message browser..."); messageBrowser = new MessageBrowser(); // Refresh code templates after extensions have been loaded @@ -1524,7 +1518,7 @@ public void alertThrowable(Component parentComponent, Throwable t, String custom } mirthClient.close(); this.dispose(); - LoginPanelFactory.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + Mirth.showLogin(); return; } else if (t.getCause() != null && t.getCause() instanceof HttpHostConnectException && (StringUtils.contains(t.getCause().getMessage(), "Connection refused") || StringUtils.contains(t.getCause().getMessage(), "Host is down"))) { connectionError = true; @@ -1542,7 +1536,7 @@ public void alertThrowable(Component parentComponent, Throwable t, String custom } mirthClient.close(); this.dispose(); - LoginPanelFactory.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + Mirth.showLogin(); return; } } @@ -2292,7 +2286,7 @@ public boolean logout(boolean quit, boolean confirmFirst) { this.dispose(); if (!quit) { - LoginPanelFactory.getInstance().initialize(PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, "", ""); + Mirth.showLogin(); } return true; diff --git a/client/src/com/mirth/connect/client/ui/LoginPanel.java b/client/src/com/mirth/connect/client/ui/LoginPanel.java new file mode 100644 index 000000000..de439c841 --- /dev/null +++ b/client/src/com/mirth/connect/client/ui/LoginPanel.java @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2025 Mitch Gaffigan +// SPDX-FileCopyrightText: 2026 Tony Germano + +package com.mirth.connect.client.ui; + +import com.mirth.connect.client.core.Client; +import com.mirth.connect.client.core.ClientException; +import com.mirth.connect.model.LoginStatus; + +/** + * Abstraction for the login panel so that alternative UI toolkit implementations + * (e.g. JavaFX) can replace the default Swing-based panel. + * + *

Discovery

+ * Implementations are discovered at runtime via {@link java.util.ServiceLoader}. + * To provide a custom login panel, add a + * {@code META-INF/services/com.mirth.connect.client.ui.LoginPanel} file whose + * single line is the fully-qualified name of the implementing class. + * The custom class must have a public no-arg constructor (required by + * {@link java.util.ServiceLoader}). + * If multiple custom implementations are found, the first one discovered + * is used (ordering depends on the classloader). + * If no custom implementation is found on the classpath, + * {@link DefaultLoginPanel} (the built-in Swing implementation) is used as + * a hardcoded fallback. {@code DefaultLoginPanel} itself is not + * registered as an SPI provider and uses a package-private constructor + * (it is instantiated directly by {@link LoginPanelFactory}). + * + *

Lifecycle

+ *
    + *
  1. The panel is instantiated once by {@link LoginPanelFactory}.
  2. + *
  3. {@link #initialize} is called each time a login screen is needed + * (initial startup and after logout / connection errors). + * If {@code user} and {@code pass} are both non-empty the implementation + * should attempt an automatic login.
  4. + *
  5. During the boot sequence after a successful login, + * {@link #setStatus} is called repeatedly to report loading progress.
  6. + *
  7. Once the main application frame is ready, {@link #setVisible(boolean) setVisible(false)} + * is called to hide the panel.
  8. + *
+ * + *

Multi-Factor Authentication

+ * Existing {@link com.mirth.connect.plugins.MultiFactorAuthenticationClientPlugin} + * implementations expect a {@link java.awt.Window} to be passed as their parent + * for dialog display. A Swing-based login panel (such as {@link DefaultLoginPanel}) + * can pass itself directly. Non-Swing implementations are free to define their + * own MFA workflow instead of using the plugin API. + * + *

Threading

+ * All methods on this interface ({@link #initialize}, {@link #setStatus}, + * {@link #setVisible}, {@link #isVisible}, and {@link #showLoginNotification}) + * may be called from any thread. Implementations must handle any necessary + * thread marshalling internally (e.g. posting to the UI thread). + *

+ * The thread from which the {@link LoginSuccessHandler} is invoked is + * determined by the implementation. {@link DefaultLoginPanel} calls the + * handler from a background thread. + * The handler (and anything it calls) must not assume it is on a UI thread. + * + * @see LoginPanelFactory + * @see DefaultLoginPanel + */ +public interface LoginPanel { + + /** + * Callback invoked by the login panel after the server confirms a + * successful authentication. + * + * @see #initialize + */ + @FunctionalInterface + interface LoginSuccessHandler { + /** + * Handle a successful login. + * + * @param client the authenticated {@link Client} connection + * @param loginStatus the status returned by the server + * @param userName the username that was used to log in + * @return {@code true} if the application booted successfully; + * {@code false} if the login flow should restart + * (e.g. the user cancelled a first-login dialog) + * @throws ClientException if a server communication error occurs + */ + boolean handle(Client client, LoginStatus loginStatus, String userName) throws ClientException; + } + + /** + * Initialize (or re-initialize) the login UI and make it visible. + *

+ * If both {@code user} and {@code pass} are non-empty, the implementation + * should start the login attempt automatically without waiting for the + * user to click a button. + *

+ * If the panel is already visible, the call should be ignored to + * prevent duplicate windows. + * + * @param mirthServer the server URL to display / connect to + * @param version the client version string, used in the window title + * @param user pre-filled username (may be empty) + * @param pass pre-filled password (may be empty) + * @param onSuccess callback to invoke after a successful authentication + */ + void initialize(String mirthServer, String version, String user, String pass, LoginSuccessHandler onSuccess); + + /** + * Update the status text shown on the login UI. + *

+ * Called repeatedly during the post-login boot sequence to report + * loading progress (e.g. "Loading extensions…"). + * Implementations must be safe to call from any thread. + * + * @param status the progress message to display + */ + void setStatus(String status); + + /** + * Show or hide the login panel. + *

+ * After the main application frame is ready, {@code setVisible(false)} + * is called to dismiss the login UI. Implementations must be safe to + * call from any thread. + * + * @param visible {@code true} to show, {@code false} to hide + */ + void setVisible(boolean visible); + + /** + * Returns whether the login panel is currently visible. + * + * @return {@code true} if the panel is showing + */ + boolean isVisible(); + + /** + * Display a modal login-notification banner and wait for the user to + * accept or cancel. + *

+ * This is called after a successful authentication when the server has + * login notifications enabled. If the user does not accept, the + * login flow is aborted and restarted. + * + * @param title the dialog title + * @param message the notification body text + * @return {@code true} if the user accepted the notification, + * {@code false} if they cancelled + */ + boolean showLoginNotification(String title, String message); +} diff --git a/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java b/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java index 9601ad0d1..83bb5c3f5 100644 --- a/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java +++ b/client/src/com/mirth/connect/client/ui/LoginPanelFactory.java @@ -1,23 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2025-2026 Open Integration Engine Contributors + package com.mirth.connect.client.ui; +import java.util.ServiceLoader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + /** - * Factory for obtaining the application's LoginPanel implementation. + * Factory for obtaining the application's {@link LoginPanel} implementation. + * Uses {@link ServiceLoader} to discover custom implementations, falling back + * to {@link DefaultLoginPanel} if none are found. */ public class LoginPanelFactory { - private static AbstractLoginPanel provider = null; - - public static synchronized AbstractLoginPanel getInstance() { - if (provider == null) { - provider = new DefaultLoginPanel(); - } - return provider; - } + private static final Logger logger = LogManager.getLogger(LoginPanelFactory.class); + private static LoginPanel loginPanel = null; /** - * Replace the current provider. This is used to switch between login implementations at runtime. + * Returns the singleton {@link LoginPanel} instance, discovering it via + * {@link ServiceLoader} on first call. If multiple custom implementations + * are found, the first one discovered is used (ordering is classloader-dependent). + * Falls back to {@link DefaultLoginPanel} if no custom implementation is + * registered. {@code DefaultLoginPanel} itself should not be registered + * as an SPI provider — it is the hardcoded fallback. */ - public static synchronized void setProvider(AbstractLoginPanel newProvider) { - provider = newProvider; + public static synchronized LoginPanel getLoginPanel() { + if (loginPanel == null) { + loginPanel = ServiceLoader.load(LoginPanel.class) + .findFirst() + .orElseGet(DefaultLoginPanel::new); + logger.info("Using LoginPanel: {}", loginPanel.getClass().getName()); + } + return loginPanel; } } diff --git a/client/src/com/mirth/connect/client/ui/Mirth.java b/client/src/com/mirth/connect/client/ui/Mirth.java index 76206ec61..b5dc9410c 100644 --- a/client/src/com/mirth/connect/client/ui/Mirth.java +++ b/client/src/com/mirth/connect/client/ui/Mirth.java @@ -69,8 +69,9 @@ public Mirth(Client mirthClient) throws ClientException { UIManager.put("Tree.closedIcon", UIConstants.CLOSED_ICON); userPreferences = Preferences.userNodeForPackage(Mirth.class); - LoginPanelFactory.getInstance().setStatus("Loading components..."); - PlatformUI.MIRTH_FRAME.setupFrame(mirthClient); + LoginPanel loginPanel = LoginPanelFactory.getLoginPanel(); + loginPanel.setStatus("Loading components..."); + PlatformUI.MIRTH_FRAME.setupFrame(mirthClient, loginPanel::setStatus); boolean maximized; int width; @@ -129,7 +130,7 @@ public static void aboutMac() { * @return quit */ public static boolean quitMac() { - return (LoginPanelFactory.getInstance().isVisible() || (PlatformUI.MIRTH_FRAME != null && PlatformUI.MIRTH_FRAME.logout(true))); + return (LoginPanelFactory.getLoginPanel().isVisible() || (PlatformUI.MIRTH_FRAME != null && PlatformUI.MIRTH_FRAME.logout(true))); } /** @@ -291,20 +292,29 @@ private static void start(final String server, final String version, final Strin public void run() { initUIManager(); PlatformUI.BACKGROUND_IMAGE = new ImageIcon(com.mirth.connect.client.ui.Frame.class.getResource("images/header_nologo.png")); - LoginPanelFactory.getInstance().initialize(server, version, username, password); + showLogin(username, password); } }); } - public static boolean handleLoginSuccess(Client client, LoginStatus loginStatus, String userName) throws ClientException { - AbstractLoginPanel loginPanel = LoginPanelFactory.getInstance(); + static void showLogin() { + showLogin("", ""); + } + + static void showLogin(String user, String pass) { + LoginPanelFactory.getLoginPanel().initialize( + PlatformUI.SERVER_URL, PlatformUI.CLIENT_VERSION, user, pass, + Mirth::handleLoginSuccess); + } + + private static boolean handleLoginSuccess(Client client, LoginStatus loginStatus, String userName) throws ClientException { + LoginPanel loginPanel = LoginPanelFactory.getLoginPanel(); try { PublicServerSettings publicServerSettings = client.getPublicServerSettings(); if (publicServerSettings.getLoginNotificationEnabled() == true) { - CustomBannerPanelDialog customBannerPanelDialog = new CustomBannerPanelDialog(loginPanel, "Login Notification", publicServerSettings.getLoginNotificationMessage()); - boolean isAccepted = customBannerPanelDialog.isAccepted(); - + boolean isAccepted = loginPanel.showLoginNotification("Login Notification", publicServerSettings.getLoginNotificationMessage()); + if (isAccepted == true) { client.setUserNotificationAcknowledged(client.getCurrentUser().getId()); }