Skip to content
Advertisement

Login dialog window won’t dispose completely

I’ve got a dialog window for logging in a user to a main program in Java. I’ve recently discovered, however, that if the user clicks the “Cancel” button or the window’s native Close button, the program still runs, even if the login window itself has been disposed of. I have to force quit it. My instincts tell me that it has to do with the LoginService that is created as part of creating a JXLoginPane.

Here is my (well-documented) code:

package info.chrismcgee.sky;

import info.chrismcgee.beans.Login;
import info.chrismcgee.dbutil.LoginConnectionManager;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog extends JDialog {
    
    // For logging!
    static final Logger log = LogManager.getLogger(LoginDialog.class.getName());

    /**
     * Serialize, to keep Eclipse from throwing a warning message.
     */
    private static final long serialVersionUID = 52954843540592996L;
    private final JPanel contentPanel = new JPanel(); // default.
    // The login pane is a field because it needs to be called
    // by the "OK" button later.
    private JXLoginPane loginPane;
    // User bean is a field because two inner methods need to access it,
    // and it would be cheaper to only create one bean.
    private Login bean;

    /**
     * Launch the application.
     * Unedited.
     */
    public static void main(String[] args) {
        
        log.entry("main (LoginDialog)");
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                
                log.entry("run (LoginDialog)");
                
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    LoginDialog dialog = new LoginDialog();
                    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
                    dialog.setVisible(true);
                } catch (Exception e) {
                    log.error("Error running the LoginDialog", e);
                }
                
                log.exit("run (LoginDialog)");
            }
        });
        
        log.exit("main (LoginDialog)");
    }

    /**
     * Create the dialog.
     */
    public LoginDialog() {
        setBounds(100, 100, 450, 300);
        getContentPane().setLayout(new BorderLayout()); // default.
        contentPanel.setLayout(new FlowLayout()); // default.
        contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); // default.
        getContentPane().add(contentPanel, BorderLayout.CENTER); // default.
        {
            // Create a JXLoginPane using a LoginService
            // to handle the authentication.
            loginPane = new JXLoginPane(new LoginService() {
                
                // `authenticate` *must* be overridden.
                // We will not be using the "server" string, however.
                @Override
                public boolean authenticate(String name, char[] password, String server)
                        throws Exception {
                    
                    log.entry("authenticate (LoginDialog)");
                    
                    // With the username entered by the user, get the user information
                    // from the database and store it in a Login bean.
                    bean = LoginManager.getRow(name);
                    
                    // If the user does not exist in the database, the bean will be null.
                    if (bean != null)
                        // Use `PasswordHash`'s `validatePassword` static method
                        // to see if the entered password (after being hashed)
                        // matches the hashed password stored in the bean.
                        // Returns `true` if it matches, `false` if it doesn't.
                        return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
                    else
                        // If the user doesn't exist in the database, just return `false`,
                        // as if the password was wrong. This way, the user isn't alerted
                        // as to which of the two pieces of credentials is wrong.
                        return log.exit(false);
                }
            });
            // Add the login pane to the main content panel.
            contentPanel.add(loginPane);
        }
        {
            // Create the button pane for the bottom of the dialog.
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); // default.
            getContentPane().add(buttonPane, BorderLayout.SOUTH); // default.
            {
                // Create the "OK" button plus its action listener.
                JButton okButton = new JButton("OK");
                okButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {
                        
                        log.entry("OK button pressed. (LoginDialog)");
                        
                        // Several of these will throw exceptions,
                        // so it's in a `try-catch` block.
                        try {
                            // This `if` statement is what calls the `authenticate`
                            // method to see if the credentials match the database.
                            if (loginPane.getLoginService().authenticate(
                                    loginPane.getUserName().toLowerCase(),
                                    loginPane.getPassword(), null))
                            {
                                // If the credentials are in order, close the connection
                                // to the `logins` database, since they won't be needed anymore.
                                LoginConnectionManager.getInstance().close();
                                // Also close the login window; it won't be needed anymore, either.
                                Window window = SwingUtilities.windowForComponent(contentPanel);
                                window.dispose();
                                
                                log.trace("Running Scheduling with access level of " + bean.getAccessLevel());
                                // And finally run the main `Scheduling.java` program,
                                // passing to it the user's access level.
                                String[] args = {Integer.toString(bean.getAccessLevel())};
                                Scheduling.main(args);
                            }
                            else
                            {
                                // If the login credentials fail, let the user know generically.
                                JOptionPane.showMessageDialog(null, "Incorrect username or password.", "Bad Username or Password", JOptionPane.ERROR_MESSAGE);
                            }
                        } catch (Exception e) {
                            log.error("Exception when hitting the 'OK' button.", e);
                        }
                        
                        log.exit("OK button done.");
                    }
                });
                okButton.setActionCommand("OK"); // default.
                // Add the "OK" button the button pane.
                buttonPane.add(okButton); // default.
                getRootPane().setDefaultButton(okButton); // default.
            }
            {
                // Create the "Cancel" button plus its listener.
                JButton cancelButton = new JButton("Cancel");
                cancelButton.setActionCommand("Cancel"); // default.
                cancelButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        
                        log.entry("CANCEL button pressed. (LoginDialog)");
                        
                        // Just close the connection to the database,
                        LoginConnectionManager.getInstance().close();
                        // and close the login window.
                        Window window = SwingUtilities.windowForComponent((Component) e.getSource());
                        window.dispose();
                        
//                      System.exit(0);
                        log.exit("CANCEL button done.");
                    }
                });
                // Add the "Cancel" button to the button pane.
                buttonPane.add(cancelButton); // default.
            }
        }
    }

}

I assume that the LoginService created a thread when the dialog was created. What is the proper way to end this thread, if that’s the case? And if it’s not the case, what’s going on and how can I fix it?

Edit

I’ve taken dic19’s advice and simplified things. Greatly. I’ve created a barebones dialog now using his recommendation of just sticking with the built-in JXLoginDialog. It doesn’t have all the methods that are needed for actually logging into my main program, but here’s that simplified dialog, which should be enough to see if the program continues running after the “Cancel” button is pressed:

package info.chrismcgee.sky;

import info.chrismcgee.beans.Login;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import javax.swing.JDialog;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog {

    static final Logger log = LogManager.getLogger(LoginDialogOriginal.class.getName());
    private static Login bean;
    private static LoginService loginService = new LoginService() {
        
        @Override
        public boolean authenticate(String name, char[] password, String server)
                throws Exception {
            
            log.entry("authenticate (LoginDialogOriginal)");
            
            bean = LoginManager.getRow(name);
            
            if (bean != null)
                return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
            else
                return log.exit(false);
        }
    };
    
    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        try {
            JXLoginPane loginPane = new JXLoginPane(loginService);
            JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(new JDialog(), loginPane);
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

I’ve removed comments to tighten up the code just a bit more.

Sadly, while clicking “Cancel” does dispose of the dialog, the program is still in memory and I have to force-quit it, as before. Perhaps there’s a thread or something going on in PasswordHash? I more or less just lifted the code from CrackStation’s post for that.

Edit 2

I’ve tried updating that simplified program, based on dic19’s recommendation of creating a JFrame that is then either shown or disposed of based on the success of the login dialog. I’m afraid this still does not change the program hanging around after the user clicks on “Cancel”. It’s still in memory and still needs to be force-quitted. Here’s the updated code with dic19’s modifications:

package info.chrismcgee.sky;

import java.awt.event.WindowEvent;

import info.chrismcgee.beans.Login;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import javax.swing.JDialog;
import javax.swing.JFrame;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog {

    static final Logger log = LogManager.getLogger(LoginDialogOriginal.class.getName());
    private static Login bean;
    private static LoginService loginService = new LoginService() {
        
        @Override
        public boolean authenticate(String name, char[] password, String server)
                throws Exception {
            
            log.entry("authenticate (LoginDialogOriginal)");

            bean = LoginManager.getRow(name);
            
            if (bean != null)
                return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
            else
                return log.exit(false);
        }
    };
    
    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        try {
            JFrame frame = new JFrame("Welcome!"); // A non-visible JFrame which acts as the parent frame.
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            JXLoginPane loginPane = new JXLoginPane(loginService);
            JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(frame, loginPane);
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);
            
            if (dialog.getStatus() != JXLoginPane.Status.SUCCEEDED)
                frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
            else
                frame.setVisible(true);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Again, I’ve removed comments for brevity. Any idea why this is still causing the program to hang around in memory after clicking “Cancel”?

Advertisement

Answer

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  // To quit the whole GUI.

or

dialog.dispose(); // After user clicks cancel.

But as the cancel button is not explicitly created by you, you cannot call dialog.dispose() directly. BUT you still can. with 3 extra lines of code. to call dialog.dispose(), add a window listener(extend java.awt.event.WindowAdapter) on your dialog. and override the windowClosing() method. and write dialog.dispose() in its body.

Snippet:

dialog.addWindowListener(

    new WindowAdapter() {

        @Override
        public void windowClosing(WindowEvent event) {

            dialog.dispose();
        }
    }
);  
3 People found this is helpful
Advertisement