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 }