001    /*
002     * $Id: JXLoginPanel.java,v 1.16 2006/04/19 22:59:43 gfx Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx;
022    
023    import java.awt.BorderLayout;
024    import java.awt.Color;
025    import java.awt.Component;
026    import java.awt.ComponentOrientation;
027    import java.awt.Cursor;
028    import java.awt.Dialog;
029    import java.awt.Dimension;
030    import java.awt.Font;
031    import java.awt.Frame;
032    import java.awt.GridBagConstraints;
033    import java.awt.GridBagLayout;
034    import java.awt.Image;
035    import java.awt.Insets;
036    import java.awt.Window;
037    import java.awt.event.ActionEvent;
038    import java.awt.event.ActionListener;
039    import java.awt.event.ItemEvent;
040    import java.awt.event.KeyEvent;
041    import java.awt.event.WindowAdapter;
042    import java.beans.PropertyChangeEvent;
043    import java.beans.PropertyChangeListener;
044    import java.util.ArrayList;
045    import java.util.Collections;
046    import java.util.Enumeration;
047    import java.util.List;
048    import java.util.ResourceBundle;
049    import java.util.logging.Level;
050    import java.util.logging.Logger;
051    import javax.swing.AbstractListModel;
052    import javax.swing.Action;
053    import javax.swing.BorderFactory;
054    
055    import javax.swing.ComboBoxModel;
056    import javax.swing.DefaultComboBoxModel;
057    import javax.swing.JButton;
058    import javax.swing.JCheckBox;
059    import javax.swing.JComboBox;
060    import javax.swing.JComponent;
061    import javax.swing.JDialog;
062    import javax.swing.JFrame;
063    import javax.swing.JLabel;
064    import javax.swing.JPasswordField;
065    import javax.swing.JProgressBar;
066    import javax.swing.JTextField;
067    import javax.swing.KeyStroke;
068    import javax.swing.SwingConstants;
069    import javax.swing.UIManager;
070    import org.jdesktop.swingx.action.AbstractActionExt;
071    
072    import org.jdesktop.swingx.auth.DefaultUserNameStore;
073    import org.jdesktop.swingx.auth.LoginAdapter;
074    import org.jdesktop.swingx.auth.LoginEvent;
075    import org.jdesktop.swingx.auth.LoginService;
076    import org.jdesktop.swingx.auth.PasswordStore;
077    import org.jdesktop.swingx.auth.UserNameStore;
078    import org.jdesktop.swingx.plaf.JXLoginPanelAddon;
079    import org.jdesktop.swingx.plaf.LoginPanelUI;
080    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
081    import org.jdesktop.swingx.util.WindowUtils;
082    /**
083     *  <p>JXLoginPanel is a JPanel that implements a Login dialog with
084     *  support for saving passwords supplied for future use in a secure
085     *  manner. It is intended to work with <strong>LoginService</strong>
086     *  and <strong>PasswordStore</strong> to implement the
087     *  authentication.</p>
088     *
089     *  <p> In order to perform the authentication, <strong>JXLoginPanel</strong>
090     *  calls the <code>authenticate</code> method of the <strong>LoginService
091     *  </strong>. In order to perform the persistence of the password,
092     *  <strong>JXLoginPanel</strong> calls the put method of the
093     *  <strong>PasswordStore</strong> object that is supplied. If
094     *  the <strong>PasswordStore</strong> is <code>null</code>, then the password
095     *  is not saved. Similarly, if a <strong>PasswordStore</strong> is
096     *  supplied and the password is null, then the <strong>PasswordStore</strong>
097     *  will be queried for the password using the <code>get</code> method.
098     *
099     * @author Bino George
100     * @author Shai Almog
101     * @author rbair
102     */
103    
104    public class JXLoginPanel extends JXImagePanel {
105        /**
106         * The Logger
107         */
108        private static final Logger LOG = Logger.getLogger(JXLoginPanel.class.getName());
109        /**
110         * Comment for <code>serialVersionUID</code>
111         */
112        private static final long serialVersionUID = 3544949969896288564L;
113        /**
114         * UI Class ID
115         */
116        public final static String uiClassID = "LoginPanelUI";
117        /**
118         * Action key for an Action in the ActionMap that initiates the Login
119         * procedure
120         */
121        public static final String LOGIN_ACTION_COMMAND = "login";
122        /** 
123         * Action key for an Action in the ActionMap that cancels the Login
124         * procedure
125         */
126        public static final String CANCEL_LOGIN_ACTION_COMMAND = "cancel-login";
127        /**
128         * The JXLoginPanel can attempt to save certain user information such as
129         * the username, password, or both to their respective stores.
130         * This type specifies what type of save should be performed.
131         */
132        public static enum SaveMode {NONE, USER_NAME, PASSWORD, BOTH};
133        /**
134         * Returns the status of the login process
135         */
136        public enum Status {NOT_STARTED, IN_PROGRESS, FAILED, CANCELLED, SUCCEEDED};
137        /**
138         * Used as a prefix when pulling data out of UIManager for i18n
139         */
140        private static String CLASS_NAME;
141    
142        /**
143         * The current login status for this panel
144         */
145        private Status status = Status.NOT_STARTED;
146        /**
147         * An optional banner at the top of the panel
148         */
149        private JXImagePanel banner;
150        /**
151         * Text that should appear on the banner
152         */
153        private String bannerText = "Login";
154        /**
155         * Custom label allowing the developer to display some message to the user
156         */
157        private JLabel messageLabel;
158        /**
159         * Shows an error message such as "user name or password incorrect" or
160         * "could not contact server" or something like that if something
161         * goes wrong
162         */
163        private JLabel errorMessageLabel;
164        /**
165         * A Panel containing all of the input fields, check boxes, etc necessary
166         * for the user to do their job. The items on this panel change whenever
167         * the SaveMode changes, so this panel must be recreated at runtime if the
168         * SaveMode changes. Thus, I must maintain this reference so I can remove
169         * this panel from the content panel at runtime.
170         */
171        private JXPanel loginPanel;
172        /**
173         * The panel on which the input fields, messageLabel, and errorMessageLabel
174         * are placed. While the login thread is running, this panel is removed
175         * from the dialog and replaced by the progressPanel
176         */
177        private JXPanel contentPanel;
178        /**
179         * This is the area in which the name field is placed. That way it can toggle on the fly
180         * between text field and a combo box depending on the situation, and have a simple
181         * way to get the user name
182         */
183        private NameComponent namePanel;
184        /**
185         * The password field presented allowing the user to enter their password
186         */
187        private JPasswordField passwordField;
188        /**
189         * A combo box presenting the user with a list of servers to which they
190         * may log in. This is an optional feature, which is only enabled if
191         * the List of servers supplied to the JXLoginPanel has a length greater
192         * than 1.
193         */
194        private JComboBox serverCombo;
195        /**
196         * Check box presented if a PasswordStore is used, allowing the user to decide whether to
197         * save their password
198         */
199        private JCheckBox saveCB;
200        /**
201         * A special panel that displays a progress bar and cancel button, and
202         * which notify the user of the login process, and allow them to cancel
203         * that process.
204         */
205        private JXPanel progressPanel;
206        /**
207         * A JLabel on the progressPanel that is used for informing the user
208         * of the status of the login procedure (logging in..., cancelling login...)
209         */
210        private JLabel progressMessageLabel;
211        /**
212         * The LoginService to use. This must be specified for the login dialog to operate.
213         * If no LoginService is defined, a default login service is used that simply
214         * allows all users access. This is useful for demos or prototypes where a proper login
215         * server is not available.
216         */
217        private LoginService loginService;
218        /**
219         * Optional: a PasswordStore to use for storing and retrieving passwords for a specific
220         * user.
221         */
222        private PasswordStore passwordStore;
223        /**
224         * Optional: a UserNameStore to use for storing user names and retrieving them
225         */
226        private UserNameStore userNameStore;
227        /**
228         * A list of servers where each server is represented by a String. If the
229         * list of Servers is greater than 1, then a combo box will be presented to
230         * the user to choose from. If any servers are specified, the selected one
231         * (or the only one if servers.size() == 1) will be passed to the LoginService
232         */
233        private List<String> servers;
234        /**
235         *  Whether to save password or username or both
236         */
237        private SaveMode saveMode;
238        /**
239         * Listens to login events on the LoginService. Updates the UI and the
240         * JXLoginPanel.state as appropriate
241         */
242        private LoginListenerImpl loginListener;
243        /**
244         * Tracks the cursor at the time that authentication was started, and restores to that
245         * cursor after authentication ends, or is cancelled;
246         */
247        private Cursor oldCursor;
248        
249        /**
250         * Creates a default JXLoginPanel instance
251         */
252        static {
253            LookAndFeelAddons.contribute(new JXLoginPanelAddon());
254            // Popuplate UIDefaults with the localizable Strings we will use
255            // in the Login panel.
256            CLASS_NAME = JXLoginPanel.class.getCanonicalName();
257            String lookup;
258            ResourceBundle res = ResourceBundle.getBundle("org.jdesktop.swingx.auth.resources.resources");
259            Enumeration<String> keys = res.getKeys();
260            while (keys.hasMoreElements()) {
261                String key = keys.nextElement();
262                lookup = CLASS_NAME + "." + key;
263                if (UIManager.getString(lookup) == null) {
264                    UIManager.put(lookup, res.getString(key));
265                }
266            }
267        }
268        
269        //--------------------------------------------------------- Constructors
270        /**
271         * Create a new JXLoginPanel
272         */
273        public JXLoginPanel() {
274            this(null);
275        }
276        
277        /**
278         * Create a new JXLoginPanel
279         * @param service The LoginService to use for logging in
280         */
281        public JXLoginPanel(LoginService service) {
282            this(service, null, null);
283        }
284        
285        /**
286         * Create a new JXLoginPanel
287         * @param service
288         * @param passwordStore
289         * @param userStore
290         */
291        public JXLoginPanel(LoginService service, PasswordStore passwordStore, UserNameStore userStore) {
292            this(service, passwordStore, userStore, null);
293        }
294        
295        /**
296         * Create a new JXLoginPanel
297         * @param service
298         * @param passwordStore
299         * @param userStore
300         * @param servers
301         */
302        public JXLoginPanel(LoginService service, PasswordStore passwordStore, UserNameStore userStore, List<String> servers) {
303            this.loginService = service == null ? new NullLoginService() : service;
304            this.passwordStore = passwordStore == null ? new NullPasswordStore() : passwordStore;
305            this.userNameStore = userStore == null ? new DefaultUserNameStore() : userStore;
306            this.servers = servers == null ? new ArrayList<String>() : servers;
307            
308            //create the login and cancel actions, and add them to the action map
309            getActionMap().put(LOGIN_ACTION_COMMAND, createLoginAction());
310            getActionMap().put(CANCEL_LOGIN_ACTION_COMMAND, createCancelAction());
311            
312            //initialize the save mode
313            if (passwordStore != null && userStore != null) {
314                saveMode = SaveMode.BOTH;
315            } else if (passwordStore != null) {
316                saveMode = SaveMode.PASSWORD;
317            } else if (userStore != null) {
318                saveMode = SaveMode.USER_NAME;
319            } else {
320                saveMode = SaveMode.NONE;
321            }
322            
323            loginListener = new LoginListenerImpl();
324            this.loginService.addLoginListener(loginListener);
325            
326            // initialize banner text
327            bannerText = UIManager.getString(CLASS_NAME + ".bannerString");
328            
329            updateUI();
330            initComponents();
331        }
332    
333        //------------------------------------------------------------- UI Logic
334        
335        /**
336         * @inheritDoc
337         */
338        public LoginPanelUI getUI() {
339            return (LoginPanelUI)super.getUI();
340        }
341    
342        /**
343         * Returns the name of the L&F class that renders this component.
344         *
345         * @return the string {@link #uiClassID}
346         * @see javax.swing.JComponent#getUIClassID
347         * @see javax.swing.UIDefaults#getUI
348         */
349        public String getUIClassID() {
350            return uiClassID;
351        }
352    
353        /**
354         * Recreates the login panel, and replaces the current one with the new one
355         */
356        protected void recreateLoginPanel() {
357            contentPanel.remove(loginPanel);
358            loginPanel = createLoginPanel();
359            loginPanel.setBorder(BorderFactory.createEmptyBorder(0, 36, 7, 11));
360            contentPanel.add(loginPanel, 1);
361        }
362        
363        /**
364         * Creates and returns a new LoginPanel, based on the SaveMode state of
365         * the login panel. Whenever the SaveMode changes, the panel is recreated.
366         * I do this rather than hiding/showing components, due to a cleaner
367         * implementation (no invisible components, components are not sharing
368         * locations in the LayoutManager, etc).
369         */
370        private JXPanel createLoginPanel() {
371            JXPanel loginPanel = new JXPanel();
372            
373            //create the NameComponent
374            if (saveMode == SaveMode.NONE) {
375                namePanel = new SimpleNamePanel();
376            } else {
377                namePanel = new ComboNamePanel(userNameStore);
378            }
379            JLabel nameLabel = new JLabel(UIManager.getString(CLASS_NAME + ".nameString"));
380            nameLabel.setLabelFor(namePanel.getComponent());
381            
382            //create the password component
383            passwordField = new JPasswordField("", 15);
384            JLabel passwordLabel = new JLabel(UIManager.getString(CLASS_NAME + ".passwordString"));
385            passwordLabel.setLabelFor(passwordField);
386            
387            //create the server combo box if necessary
388    //            JLabel serverLabel = new JLabel(UIManager.getString(CLASS_NAME + ".serverString"));
389            JLabel serverLabel = new JLabel("Server");
390            if (servers.size() > 1) {
391                serverCombo = new JComboBox(servers.toArray());
392                serverLabel.setLabelFor(serverCombo);
393            } else {
394                serverCombo = null;
395            }
396            
397            //create the save check box. By default, it is not selected
398            saveCB = new JCheckBox(UIManager.getString(CLASS_NAME + ".rememberPasswordString"));
399            saveCB.setSelected(false); //TODO should get this from prefs!!! And, it should be based on the user
400            //determine whether to show/hide the save check box based on the SaveMode
401            saveCB.setVisible(saveMode == SaveMode.PASSWORD || saveMode == SaveMode.BOTH);
402            
403            loginPanel.setLayout(new GridBagLayout());
404            GridBagConstraints gridBagConstraints = new GridBagConstraints();
405            gridBagConstraints.gridx = 0;
406            gridBagConstraints.gridy = 0;
407            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
408            gridBagConstraints.insets = new Insets(0, 0, 5, 11);
409            loginPanel.add(nameLabel, gridBagConstraints);
410            
411            gridBagConstraints = new GridBagConstraints();
412            gridBagConstraints.gridx = 1;
413            gridBagConstraints.gridy = 0;
414            gridBagConstraints.gridwidth = 1;
415            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
416            gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
417            gridBagConstraints.weightx = 1.0;
418            gridBagConstraints.insets = new Insets(0, 0, 5, 0);
419            loginPanel.add(namePanel.getComponent(), gridBagConstraints);
420            
421            gridBagConstraints = new GridBagConstraints();
422            gridBagConstraints.gridx = 0;
423            gridBagConstraints.gridy = 1;
424            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
425            gridBagConstraints.insets = new Insets(0, 0, 5, 11);
426            loginPanel.add(passwordLabel, gridBagConstraints);
427            
428            gridBagConstraints = new GridBagConstraints();
429            gridBagConstraints.gridx = 1;
430            gridBagConstraints.gridy = 1;
431            gridBagConstraints.gridwidth = 1;
432            gridBagConstraints.anchor = GridBagConstraints.LINE_START;
433            gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
434            gridBagConstraints.weightx = 1.0;
435            gridBagConstraints.insets = new Insets(0, 0, 5, 0);
436            loginPanel.add(passwordField, gridBagConstraints);
437            
438            if (serverCombo != null) {
439                gridBagConstraints = new GridBagConstraints();
440                gridBagConstraints.gridx = 0;
441                gridBagConstraints.gridy = 2;
442                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
443                gridBagConstraints.insets = new Insets(0, 0, 5, 11);
444                loginPanel.add(serverLabel, gridBagConstraints);
445    
446                gridBagConstraints = new GridBagConstraints();
447                gridBagConstraints.gridx = 1;
448                gridBagConstraints.gridy = 2;
449                gridBagConstraints.gridwidth = 1;
450                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
451                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
452                gridBagConstraints.weightx = 1.0;
453                gridBagConstraints.insets = new Insets(0, 0, 5, 0);
454                loginPanel.add(serverCombo, gridBagConstraints);
455    
456                gridBagConstraints = new GridBagConstraints();
457                gridBagConstraints.gridx = 0;
458                gridBagConstraints.gridy = 3;
459                gridBagConstraints.gridwidth = 2;
460                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
461                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
462                gridBagConstraints.weightx = 1.0;
463                gridBagConstraints.insets = new Insets(6, 0, 0, 0);
464                loginPanel.add(saveCB, gridBagConstraints);
465            } else {
466                gridBagConstraints = new GridBagConstraints();
467                gridBagConstraints.gridx = 0;
468                gridBagConstraints.gridy = 2;
469                gridBagConstraints.gridwidth = 2;
470                gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
471                gridBagConstraints.anchor = GridBagConstraints.LINE_START;
472                gridBagConstraints.weightx = 1.0;
473                gridBagConstraints.insets = new Insets(6, 0, 0, 0);
474                loginPanel.add(saveCB, gridBagConstraints);
475            }
476            return loginPanel;
477        }
478        
479        /**
480         * This method adds functionality to support bidi languages within this 
481         * component
482         */
483        public void setComponentOrientation(ComponentOrientation orient) {
484            // this if is used to avoid needless creations of the image
485            if(orient != super.getComponentOrientation()) {
486                super.setComponentOrientation(orient);
487                banner.setImage(createLoginBanner());
488                progressPanel.applyComponentOrientation(orient);
489            }
490        }
491        
492        /**
493         * Create all of the UI components for the login panel
494         */
495        private void initComponents() {
496            //create the default banner
497            banner = new JXImagePanel();
498            banner.setImage(createLoginBanner());
499    
500            //create the default label
501            messageLabel = new JLabel(" ");
502            messageLabel.setOpaque(true);
503            messageLabel.setFont(messageLabel.getFont().deriveFont(Font.BOLD));
504    
505            //create the main components
506            loginPanel = createLoginPanel();
507            
508            //create the message and hyperlink and hide them
509            errorMessageLabel = new JLabel(UIManager.getString(CLASS_NAME + ".errorMessage")); 
510            errorMessageLabel.setIcon(UIManager.getIcon("JXLoginDialog.error.icon"));
511            errorMessageLabel.setVerticalTextPosition(SwingConstants.TOP);
512            errorMessageLabel.setOpaque(true);
513            errorMessageLabel.setBackground(new Color(255, 215, 215));//TODO get from UIManager
514            errorMessageLabel.setBorder(BorderFactory.createCompoundBorder(
515                    BorderFactory.createLineBorder(errorMessageLabel.getBackground().darker()),
516                    BorderFactory.createEmptyBorder(5, 7, 5, 5))); //TODO get color from UIManager
517            errorMessageLabel.setVisible(false);
518            
519            //aggregate the optional message label, content, and error label into
520            //the contentPanel
521            contentPanel = new JXPanel(new VerticalLayout());
522            messageLabel.setBorder(BorderFactory.createEmptyBorder(12, 12, 7, 11));
523            contentPanel.add(messageLabel);
524            loginPanel.setBorder(BorderFactory.createEmptyBorder(0, 36, 7, 11));
525            contentPanel.add(loginPanel);
526            errorMessageLabel.setBorder(BorderFactory.createCompoundBorder(
527                    BorderFactory.createMatteBorder(0, 36, 0, 11, contentPanel.getBackground()),
528                    errorMessageLabel.getBorder()));
529            contentPanel.add(errorMessageLabel);
530            
531            //create the progress panel
532            progressPanel = new JXPanel(new GridBagLayout());
533            progressMessageLabel = new JLabel(UIManager.getString(CLASS_NAME + ".pleaseWait"));
534            progressMessageLabel.setFont(progressMessageLabel.getFont().deriveFont(Font.BOLD)); //TODO get from UIManager
535            JProgressBar pb = new JProgressBar();
536            pb.setIndeterminate(true);
537            JButton cancelButton = new JButton(getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND));
538            progressPanel.add(progressMessageLabel, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 12, 11, 11), 0, 0));
539            progressPanel.add(pb, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 24, 11, 7), 0, 0));
540            progressPanel.add(cancelButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 11, 11), 0, 0));
541            
542            //layout the panel
543            setLayout(new BorderLayout());
544            add(banner, BorderLayout.NORTH);
545            add(contentPanel, BorderLayout.CENTER);
546        }
547    
548        /**
549         * Create and return an image to use for the Banner. This may be overridden
550         * to return any image you like
551         */
552        protected Image createLoginBanner() {
553            return getUI() == null ? null : getUI().getBanner();
554        }
555        
556        /**
557         * Create and return an Action for logging in
558         */
559        protected Action createLoginAction() {
560        return new LoginAction(this);
561        }
562        
563        /**
564         * Create and return an Action for canceling login
565         */
566        protected Action createCancelAction() {
567        return new CancelAction(this);
568        }
569        
570        //------------------------------------------------------ Bean Properties
571        //TODO need to fire property change events!!!
572        /**
573         * @return Returns the saveMode.
574         */
575        public SaveMode getSaveMode() {
576            return saveMode;
577        }
578        
579        /**
580         * The save mode indicates whether the "save" password is checked by default. This method
581         * makes no difference if the passwordStore is null.
582         *
583         * @param saveMode The saveMode to set either SAVE_NONE, SAVE_PASSWORD or SAVE_USERNAME
584         */
585        public void setSaveMode(SaveMode saveMode) {
586            this.saveMode = saveMode;
587            recreateLoginPanel();
588        }
589        
590        /**
591         * @return the List of servers
592         */
593        public List<String> getServers() {
594            return Collections.unmodifiableList(servers);
595        }
596        
597        /**
598         * Sets the list of servers. See the servers field javadoc for more info
599         */
600        public void setServers(List<String> servers) {
601            if (this.servers != servers) {
602                List<String> old = this.servers;
603                this.servers = servers == null ? new ArrayList<String>() : servers;
604                recreateLoginPanel();
605                firePropertyChange("servers", old, servers);
606            }
607        }
608        
609        /**
610         * Sets the <strong>LoginService</strong> for this panel.
611         *
612         * @param service service
613         */
614        public void setLoginService(LoginService service) {
615            loginService = service;
616        }
617        
618        /**
619         * Gets the <strong>LoginService</strong> for this panel.
620         *
621         * @return service service
622         */
623        public LoginService getLoginService() {
624            return loginService;
625        }
626        
627        /**
628         * Sets the <strong>PasswordStore</strong> for this panel.
629         *
630         * @param store PasswordStore
631         */
632        public void setPasswordStore(PasswordStore store) {
633            passwordStore = store;
634        }
635        
636        /**
637         * Gets the <strong>PasswordStore</strong> for this panel.
638         *
639         * @return store PasswordStore
640         */
641        public PasswordStore getPasswordStore() {
642            return passwordStore;
643        }
644        
645        /**
646         * Sets the <strong>User name</strong> for this panel.
647         *
648         * @param username User name
649         */
650        public void setUserName(String username) {
651            if (namePanel != null) {
652                namePanel.setUserName(username);
653            }
654        }
655        
656        /**
657         * Gets the <strong>User name</strong> for this panel.
658         * @return the user name
659         */
660        public String getUserName() {
661            return namePanel == null ? null : namePanel.getUserName();
662        }
663        
664        /**
665         * Sets the <strong>Password</strong> for this panel.
666         *
667         * @param password Password
668         */
669        public void setPassword(char[] password) {
670            passwordField.setText(new String(password));
671        }
672        
673        /**
674         * Gets the <strong>Password</strong> for this panel.
675         *
676         * @return password Password
677         */
678        public char[] getPassword() {
679            return passwordField.getPassword();
680        }
681        
682        /**
683         * Return the image used as the banner
684         */
685        public Image getBanner() {
686            return banner.getImage();
687        }
688        
689        /**
690         * Set the image to use for the banner
691         */
692        public void setBanner(Image img) {
693            banner.setImage(img);
694        }
695        
696        /**
697         * Set the text to use when creating the banner. If a custom banner image
698         * is specified, then this is ignored
699         */
700        public void setBannerText(String text) {
701            if (text == null) {
702                text = "";
703            }
704    
705            if (!this.bannerText.equals(text)) {
706                String oldText = this.bannerText;
707                this.bannerText = text;
708                //fix the login banner
709                banner.setImage(createLoginBanner());
710                firePropertyChange("bannerText", oldText, text);
711            }
712        }
713    
714        /**
715         * Returns text used when creating the banner
716         */
717        public String getBannerText() {
718            return bannerText;
719        }
720    
721        /**
722         * Returns the custom message for this login panel
723         */
724        public String getMessage() {
725            return messageLabel.getText();
726        }
727        
728        /**
729         * Sets a custom message for this login panel
730         */
731        public void setMessage(String message) {
732            messageLabel.setText(message);
733        }
734        
735        /**
736         * Returns the error message for this login panel
737         */
738        public String getErrorMessage() {
739            return errorMessageLabel.getText();
740        }
741        
742        /**
743         * Sets the error message for this login panel
744         */
745        public void setErrorMessage(String errorMessage) {
746            errorMessageLabel.setText(errorMessage);
747        }
748        
749        /**
750         * Returns the panel's status
751         */
752        public Status getStatus() {
753            return status;
754        }
755        
756        /**
757         * Change the status
758         */
759        protected void setStatus(Status newStatus) {
760        if (status != newStatus) {
761            Status oldStatus = status;
762            status = newStatus;
763            firePropertyChange("status", oldStatus, newStatus);
764        }
765        }
766        //-------------------------------------------------------------- Methods
767        
768        /**
769         * Initiates the login procedure. This method is called internally by
770         * the LoginAction. This method handles cursor management, and actually
771         * calling the LoginService's startAuthentication method.
772         */
773        protected void startLogin() {
774            oldCursor = getCursor();
775            try {
776                setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
777                progressMessageLabel.setText(UIManager.getString(CLASS_NAME + ".pleaseWait"));
778                String name = getUserName();
779                char[] password = getPassword();
780                String server = servers.size() == 1 ? servers.get(0) : serverCombo == null ? null : (String)serverCombo.getSelectedItem();
781                loginService.startAuthentication(name, password, server);
782            } catch(Exception ex) {
783            //The status is set via the loginService listener, so no need to set
784            //the status here. Just log the error.
785            LOG.log(Level.WARNING, "Authentication exception while logging in", ex);
786            } finally {
787                setCursor(oldCursor);
788            }
789        }
790        
791        /**
792         * Cancels the login procedure. Handles cursor management and interfacing
793         * with the LoginService's cancelAuthentication method
794         */
795        protected void cancelLogin() {
796            progressMessageLabel.setText(UIManager.getString(CLASS_NAME + ".cancelWait"));
797            getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND).setEnabled(false);
798            loginService.cancelAuthentication();
799            setCursor(oldCursor);
800        }
801        
802        /**
803         * TODO
804         */
805        protected void savePassword() {
806            if (saveCB.isSelected() 
807                && (saveMode == SaveMode.BOTH || saveMode == SaveMode.PASSWORD)
808                && passwordStore != null) {
809                passwordStore.set(getUserName(),getLoginService().getServer(),getPassword());
810            }
811        }
812        
813        //--------------------------------------------- Listener Implementations
814        /*
815         
816         For Login (initiated in LoginAction):
817            0) set the status
818            1) Immediately disable the login action
819            2) Immediately disable the close action (part of enclosing window)
820            3) initialize the progress pane
821              a) enable the cancel login action
822              b) set the message text
823            4) hide the content pane, show the progress pane
824         
825         When cancelling (initiated in CancelAction):
826             0) set the status
827             1) Disable the cancel login action
828             2) Change the message text on the progress pane
829         
830         When cancel finishes (handled in LoginListener):
831             0) set the status
832             1) hide the progress pane, show the content pane
833             2) enable the close action (part of enclosing window)
834             3) enable the login action
835         
836         When login fails (handled in LoginListener):
837             0) set the status
838             1) hide the progress pane, show the content pane
839             2) enable the close action (part of enclosing window)
840             3) enable the login action
841             4) Show the error message
842             5) resize the window (part of enclosing window)
843         
844         When login succeeds (handled in LoginListener):
845             0) set the status
846             1) close the dialog/frame (part of enclosing window)
847         */
848        /**
849         * Listener class to track state in the LoginService
850         */
851        protected class LoginListenerImpl extends LoginAdapter {
852            public void loginSucceeded(LoginEvent source) {
853                //save the user names and passwords
854                String userName = namePanel.getUserName();
855                savePassword();
856                if (getSaveMode() == SaveMode.USER_NAME
857                        && userName != null && !userName.trim().equals("")) {
858                    userNameStore.addUserName(userName);
859                    userNameStore.saveUserNames();
860                }
861                setStatus(Status.SUCCEEDED);
862            }
863                
864            public void loginStarted(LoginEvent source) {
865            getActionMap().get(LOGIN_ACTION_COMMAND).setEnabled(false);
866                getActionMap().get(CANCEL_LOGIN_ACTION_COMMAND).setEnabled(true);
867                remove(contentPanel);
868                add(progressPanel, BorderLayout.CENTER);
869                revalidate();
870                repaint();
871                setStatus(Status.IN_PROGRESS);
872            }
873    
874            public void loginFailed(LoginEvent source) {
875                remove(progressPanel);
876                add(contentPanel, BorderLayout.CENTER);
877            getActionMap().get(LOGIN_ACTION_COMMAND).setEnabled(true);
878                errorMessageLabel.setVisible(true);
879                revalidate();
880                repaint();
881                setStatus(Status.FAILED);
882            }
883    
884            public void loginCanceled(LoginEvent source) {
885                remove(progressPanel);
886                add(contentPanel, BorderLayout.CENTER);
887            getActionMap().get(LOGIN_ACTION_COMMAND).setEnabled(true);
888                errorMessageLabel.setVisible(false);
889                revalidate();
890                repaint();
891                setStatus(Status.CANCELLED);
892            }
893        }
894        
895        //---------------------------------------------- Default Implementations
896        /**
897         * Action that initiates a login procedure. Delegates to JXLoginPanel.startLogin
898         */
899        private static final class LoginAction extends AbstractActionExt {
900        private JXLoginPanel panel;
901        public LoginAction(JXLoginPanel p) {
902            super(UIManager.getString(CLASS_NAME + ".loginString"), LOGIN_ACTION_COMMAND); 
903            this.panel = p;
904        }
905        public void actionPerformed(ActionEvent e) {
906            panel.startLogin();
907        }
908        public void itemStateChanged(ItemEvent e) {}
909        }
910        
911        /**
912         * Action that cancels the login procedure. 
913         */
914        private static final class CancelAction extends AbstractActionExt {
915        private JXLoginPanel panel;
916        public CancelAction(JXLoginPanel p) {
917            super(UIManager.getString(CLASS_NAME + ".cancelLogin"), CANCEL_LOGIN_ACTION_COMMAND); 
918            this.panel = p;
919            this.setEnabled(false);
920        }
921        public void actionPerformed(ActionEvent e) {
922            panel.cancelLogin();
923        }
924        public void itemStateChanged(ItemEvent e) {}
925        }
926        
927        /**
928         * Simple login service that allows everybody to login. This is useful in demos and allows
929         * us to avoid having to check for LoginService being null
930         */
931        private static final class NullLoginService extends LoginService {
932            public boolean authenticate(String name, char[] password, String server) throws Exception {
933                return true;
934            }
935        }
936        
937        /**
938         * Simple PasswordStore that does not remember passwords
939         */
940        private static final class NullPasswordStore extends PasswordStore {
941            private static final char[] EMPTY = new char[0];
942            public boolean set(String username, String server, char[] password) {
943                //null op
944                return false;
945            }
946            public char[] get(String username, String server) {
947                return EMPTY;
948            }
949        }
950        
951        //--------------------------------- Default NamePanel Implementations
952        public static interface NameComponent {
953            public String getUserName();
954            public void setUserName(String userName);
955            public JComponent getComponent();
956        }
957        
958        /**
959         * If a UserNameStore is not used, then this text field is presented allowing the user
960         * to simply enter their user name
961         */
962        public static final class SimpleNamePanel extends JTextField implements NameComponent {
963            public SimpleNamePanel() {
964                super("", 15);
965            }
966            public String getUserName() {
967                return getText();
968            }
969            public void setUserName(String userName) {
970                setText(userName);
971            }
972            public JComponent getComponent() {
973                return this;
974            }
975        }
976        
977        /**
978         * If a UserNameStore is used, then this combo box is presented allowing the user
979         * to select a previous login name, or type in a new login name
980         */
981        public static final class ComboNamePanel extends JComboBox implements NameComponent {
982            private UserNameStore userNameStore;
983            public ComboNamePanel(UserNameStore userNameStore) {
984                super();
985                this.userNameStore = userNameStore;
986                setModel(new NameComboBoxModel());
987                setEditable(true);
988            }
989            public String getUserName() {
990                Object item = getModel().getSelectedItem();
991                return item == null ? null : item.toString();
992            }
993            public void setUserName(String userName) {
994                getModel().setSelectedItem(userName);
995            }
996            public void setUserNames(String[] names) {
997                setModel(new DefaultComboBoxModel(names));
998            }
999            public JComponent getComponent() {
1000                return this;
1001            }
1002            private final class NameComboBoxModel extends AbstractListModel implements ComboBoxModel {
1003                private Object selectedItem;
1004                public void setSelectedItem(Object anItem) {
1005                    selectedItem = anItem;
1006                    fireContentsChanged(this, -1, -1);
1007                }
1008                public Object getSelectedItem() {
1009                    return selectedItem;
1010                }
1011                public Object getElementAt(int index) {
1012                    return userNameStore.getUserNames()[index];
1013                }
1014                public int getSize() {
1015                    return userNameStore.getUserNames().length;
1016                }
1017            }
1018        }
1019    
1020        //------------------------------------------ Static Construction Methods
1021        /**
1022         * Shows a login dialog. This method blocks.
1023         * @return The status of the login operation
1024         */
1025        public static Status showLoginDialog(Component parent, LoginService svc) {
1026            return showLoginDialog(parent, svc, null, null);
1027        }
1028    
1029        /**
1030         * Shows a login dialog. This method blocks.
1031         * @return The status of the login operation
1032         */
1033        public static Status showLoginDialog(Component parent, LoginService svc, PasswordStore ps, UserNameStore us) {
1034            return showLoginDialog(parent, svc, ps, us, null);
1035        }
1036        
1037        /**
1038         * Shows a login dialog. This method blocks.
1039         * @return The status of the login operation
1040         */
1041        public static Status showLoginDialog(Component parent, LoginService svc, PasswordStore ps, UserNameStore us, List<String> servers) {
1042            JXLoginPanel panel = new JXLoginPanel(svc, ps, us, servers);
1043            return showLoginDialog(parent, panel);
1044        }
1045        
1046        /**
1047         * Shows a login dialog. This method blocks.
1048         * @return The status of the login operation
1049         */
1050        public static Status showLoginDialog(Component parent, JXLoginPanel panel) {
1051            Window w = WindowUtils.findWindow(parent);
1052            JXLoginDialog dlg =  null;
1053            if (w == null) {
1054                dlg = new JXLoginDialog((Frame)null, panel);
1055            } else if (w instanceof Dialog) {
1056                dlg = new JXLoginDialog((Dialog)w, panel);
1057            } else if (w instanceof Frame) {
1058                dlg = new JXLoginDialog((Frame)w, panel);
1059            }
1060            dlg.setVisible(true);
1061            return dlg.getStatus();
1062        }
1063        
1064        /**
1065         * Shows a login frame. A JFrame is not modal, and thus does not block
1066         */
1067        public static JXLoginFrame showLoginFrame(LoginService svc) {
1068            return showLoginFrame(svc, null, null);
1069        }
1070    
1071        /**
1072         */
1073        public static JXLoginFrame showLoginFrame(LoginService svc, PasswordStore ps, UserNameStore us) {
1074            return showLoginFrame(svc, ps, us, null);
1075        }
1076        
1077        /**
1078         */
1079        public static JXLoginFrame showLoginFrame(LoginService svc, PasswordStore ps, UserNameStore us, List<String> servers) {
1080            JXLoginPanel panel = new JXLoginPanel(svc, ps, us, servers);
1081            return showLoginFrame(panel);
1082        }
1083    
1084        /**
1085         */
1086        public static JXLoginFrame showLoginFrame(JXLoginPanel panel) {
1087            return new JXLoginFrame(panel);
1088        }
1089    
1090        public static final class JXLoginDialog extends JDialog {
1091            private JXLoginPanel panel;
1092            
1093            public JXLoginDialog(Frame parent, JXLoginPanel p) {
1094                super(parent, true);
1095                init(p);
1096            }
1097            
1098            public JXLoginDialog(Dialog parent, JXLoginPanel p) {
1099                super(parent, true);
1100                init(p);
1101            }
1102            
1103        protected void init(JXLoginPanel p) {
1104            setTitle(UIManager.getString(CLASS_NAME + ".titleString")); 
1105                this.panel = p;
1106                initWindow(this, panel);
1107        }
1108        
1109        public JXLoginPanel.Status getStatus() {
1110            return panel.getStatus();
1111        }
1112        }
1113        
1114        public static final class JXLoginFrame extends JFrame {
1115        private JXLoginPanel panel;
1116        
1117        public JXLoginFrame(JXLoginPanel p) {
1118            super(UIManager.getString(CLASS_NAME + ".titleString")); 
1119            this.panel = p;
1120                initWindow(this, panel);
1121        }
1122        
1123        public JXLoginPanel.Status getStatus() {
1124            return panel.getStatus();
1125        }
1126            
1127            public JXLoginPanel getPanel() {
1128                return panel;
1129            }
1130        }
1131        
1132        /**
1133         * Utility method for initializing a Window for displaying a LoginDialog.
1134         * This is particularly useful because the differences between JFrame and
1135         * JDialog are so minor.
1136         *
1137         * Note: This method is package private for use by JXLoginDialog (proper, 
1138         * not JXLoginPanel.JXLoginDialog). Change to private if JXLoginDialog is
1139         * removed.
1140         */
1141        static void initWindow(final Window w, final JXLoginPanel panel) {
1142            w.setLayout(new BorderLayout());
1143            w.add(panel, BorderLayout.CENTER);
1144            JButton okButton = new JButton(panel.getActionMap().get(LOGIN_ACTION_COMMAND));
1145            final JButton cancelButton = new JButton(UIManager.getString(CLASS_NAME + ".cancelString"));
1146            cancelButton.addActionListener(new ActionListener() {
1147                public void actionPerformed(ActionEvent e) {
1148                    //change panel status to cancelled!
1149                    panel.status = JXLoginPanel.Status.CANCELLED;
1150                    w.setVisible(false);
1151                    w.dispose();
1152                }
1153            });
1154            panel.addPropertyChangeListener("status", new PropertyChangeListener() {
1155                public void propertyChange(PropertyChangeEvent evt) {
1156                    JXLoginPanel.Status status = (JXLoginPanel.Status)evt.getNewValue();
1157                    switch (status) {
1158                        case NOT_STARTED:
1159                            break;
1160                        case IN_PROGRESS:
1161                            cancelButton.setEnabled(false);
1162                            break;
1163                        case CANCELLED:
1164                            cancelButton.setEnabled(true);
1165                            w.pack();
1166                            break;
1167                        case FAILED:
1168                            cancelButton.setEnabled(true);
1169                            w.pack();
1170                            break;
1171                        case SUCCEEDED:
1172                            w.setVisible(false);
1173                            w.dispose();
1174                    }
1175                }
1176            });
1177            cancelButton.setText(UIManager.getString(CLASS_NAME + ".cancelString"));
1178            int prefWidth = Math.max(cancelButton.getPreferredSize().width, okButton.getPreferredSize().width);
1179            cancelButton.setPreferredSize(new Dimension(prefWidth, okButton.getPreferredSize().height));
1180            okButton.setPreferredSize(new Dimension(prefWidth, okButton.getPreferredSize().height));
1181            JXPanel buttonPanel = new JXPanel(new GridBagLayout());
1182            buttonPanel.add(okButton, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(17, 12, 11, 5), 0, 0));
1183            buttonPanel.add(cancelButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(17, 0, 11, 11), 0, 0));
1184            w.add(buttonPanel, BorderLayout.SOUTH);            
1185            w.addWindowListener(new WindowAdapter() {
1186                public void windowClosing(java.awt.event.WindowEvent e) {
1187                    panel.cancelLogin();
1188                }
1189            });
1190    
1191            if (w instanceof JFrame) {
1192                final JFrame f = (JFrame)w;
1193                f.getRootPane().setDefaultButton(okButton);
1194                f.setResizable(false);
1195                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
1196                KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
1197                ActionListener closeAction = new ActionListener() {
1198                    public void actionPerformed(ActionEvent e) {
1199                        f.setVisible(false);
1200                        f.dispose();
1201                    }
1202                };
1203                f.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW);
1204            } else if (w instanceof JDialog) {
1205                final JDialog d = (JDialog)w;
1206                d.getRootPane().setDefaultButton(okButton);
1207                d.setResizable(false);
1208                KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
1209                ActionListener closeAction = new ActionListener() {
1210                    public void actionPerformed(ActionEvent e) {
1211                        d.setVisible(false);
1212                    }
1213                };
1214                d.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW);
1215            }
1216            w.pack();
1217            w.setLocation(WindowUtils.getPointForCentering(w));
1218        }
1219    }