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 }