/*
glogin.java
Ganymede client login module
This client has been developed so that it can run as both an applet,
as well as an application.
Created: 22 Jan 1997
Module By: Navin Manohar, Mike Mulvaney, and Jonathan Abbey
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2013
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.client;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.rmi.RemoteException;
import java.util.Properties;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import arlut.csd.JDialog.JErrorDialog;
import arlut.csd.JDialog.StandardDialog;
import arlut.csd.JDialog.StringDialog;
import arlut.csd.Util.booleanSemaphore;
import arlut.csd.Util.PackageResources;
import arlut.csd.Util.ParseArgs;
import arlut.csd.Util.TranslationService;
import arlut.csd.ganymede.common.ClientMessage;
import arlut.csd.ganymede.rmi.Server;
import arlut.csd.ganymede.rmi.Session;
/*------------------------------------------------------------------------------
class
glogin
------------------------------------------------------------------------------*/
/**
* <p>Ganymede client start class. This class can be run from the command
* line via its static main() method, or as an applet loaded into a
* web browser, generally with Sun's Java plug-in.</p>
*
* <p>This class has a run() method for attempting to connect to
* the server in the background once the applet is initialized.</p>
*
* <p>Once glogin handles the user's login, a {@link arlut.csd.ganymede.client.gclient gclient}
* object is constructed, which handles all of the user's interactions with the server.</p>
*
* @author Navin Manohar, Mike Mulvaney, and Jonathan Abbey
*/
public class glogin extends JApplet implements Runnable, ActionListener, ClientListener {
public static boolean debug = false;
/**
* TranslationService object for handling string localization in the
* Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.glogin");
/**
* <p>If this boolean is set to true, when the Ganymede client is
* run as an application, the login box will hide itself away when
* the client's main frame is up.</p>
*
* <p>Unfortunately, I don't think that it's generally possible to
* duplicate this sort of behavior when running Ganymede as an
* applet, so it may be confusing to some to enable this
* behavior.</p>
*
* <p>I'm enabling it. - JDA 29 September 2005</p>
*/
public static final boolean hideLoginWhenApplication = true;
public static String
properties_file = null,
serverhost = null,
server_url = null,
helpBase = null;
public static Integer
registryPort = null;
/**
* The default registry port if we don't have one specified in a
* property file or in an applet parameter.
*/
public static int registryPortProperty = 1099;
/**
* Client-side properties loaded from the command line or from the
* web page which contains the definition for glogin as an applet.
*/
public static Properties ganymedeProperties = null;
/**
* The main client class, will be null until the user is logged in
* to the server.
*/
public static gclient g_client;
/**
* If we are run from the command line, this frame will be used to contain
* the glogin applet in an application context.
*/
protected static JFrame my_frame = null;
/**
* Reference to the server acquired by RMI naming service. Used to
* log into the server.
*/
protected static Server my_server;
/**
* RMI object to handle getting us logged into the server, and to handle
* asynchronous callbacks from the server on our behalf.
*/
protected static ClientBase my_client;
/**
* Reference to a user session on the server, will be null until the user is
* logged into the server.
*/
protected static Session my_session;
protected static String my_username, my_passwd;
protected static String active_username, active_passwd;
/**
* We're a singleton pattern.. this is a static reference to our actual login
* applet.
*/
protected static glogin my_glogin;
/**
* If true, we are running as an applet and are limited by the Java sandbox.
* A few features of the client will be disabled if this is true (saving query
* reports to disk, etc.).
*/
private static boolean WeAreApplet = true;
/**
* Background thread to handle force disconnect commands from the server.
* We need this thread because jdk 1.2 has a bug where RMI callbacks are
* not privileged to interact with the Swing thread. By creating a thread
* to handle forced logouts ourselves, we can have an RMI callback pass
* a message to this thread (which has local privileges), which can then
* throw up a dialog explaining about being disconnected, etc.
*/
protected static DeathWatcherThread deathThread;
/**
* If true, we're running on a mac, and we might tweak our interface
* a bit to make things look better.
*/
private static Boolean runningOnMac = null;
public static boolean isRunningOnMac()
{
if (runningOnMac == null)
{
runningOnMac = "Mac OS X".equals(System.getProperty("os.name"));
}
return runningOnMac;
}
// ---
/**
* Background thread used to attempt to get the initial RMI connection to the
* Ganymede server.
*/
protected Thread my_thread = new Thread(this);
/**
* The server will send us a login count message before we've got
* the client ready to receive and display it, so we'll need to
* retain the count for the client to consult on startup.
*/
private int initialLoginCount;
private GridBagLayout gbl;
private GridBagConstraints gbc;
Image errorImage = null;
protected Image ganymede_logo;
protected Image ganymede_ssl_logo;
protected JTextField username;
protected JPasswordField passwd;
protected JButton connector;
protected JButton _quitButton;
protected JPanel bPanel;
char spinAry[] = {'/','-', '\\', '|'};
int spindex = 0;
String connectError = null;
private booleanSemaphore connected = new booleanSemaphore(false);
private booleanSemaphore connecting = new booleanSemaphore(false);
private boolean ssl = false;
private JLabel image = null;
/* -- */
/**
* This main() function will allow this applet to run as an application
* when it is not executed in the context of a browser.
*/
public static void main (String args[])
{
WeAreApplet = false;
debug = ParseArgs.switchExists("-debug", args);
properties_file = ParseArgs.getArg("properties", args);
// make sure that we start the Ganymede client on the GUI thread
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
my_glogin = new glogin();
my_frame = new gloginFrame(ts.l("main.frame_name"), // "Ganymede Client"
my_glogin);
my_frame.getContentPane().setLayout(new BorderLayout());
my_frame.getContentPane().add("Center", my_glogin);
my_glogin.init(); // init before we setVisible(), so startup is smoother
my_frame.pack(); // pack so that we fit everything properly
my_frame.setLocationRelativeTo(null); // center on the screen, please
my_frame.setVisible(true);
}
});
}
/**
* This method returns true if the Ganymede client is running
* as an applet.
*/
public static boolean isApplet()
{
return WeAreApplet;
}
/**
* <p>Returns a configuration String from a property file or applet
* parameter element, depending on whether the Ganymede client is
* being run as an application or as an applet.</p>
*
* <p>If glogin is being run as an application, the static variable
* WeAreApplet must be set to false, and properties_file should be
* set to point to the Ganymede property file on disk before
* getConfigString() is called.</p>
*
* <p>If glogin is being run as an applet, the static variable my_login
* must be set to point to the singleton glogin object before
* getConfigString() is called.</p>
*/
static public String getConfigString(String configKey)
{
if (WeAreApplet)
{
return my_glogin.getParameter(configKey);
}
else
{
if (properties_file != null)
{
if (ganymedeProperties == null)
{
// declare a local Properties object while we're
// initializing it so that the static
// ganymedeProperties field is not visible externally
// in mid-configuration. FindBugs.
Properties myGanymedeProperties = new Properties();
if (debug)
{
System.out.println("Loading properties from: " + properties_file);
}
if (properties_file != null)
{
BufferedInputStream bis = null;
try
{
bis = new BufferedInputStream(new FileInputStream(properties_file));
myGanymedeProperties.load(bis);
}
catch (java.io.FileNotFoundException e)
{
throw new RuntimeException("File not found: " + e);
}
catch (java.io.IOException e)
{
throw new RuntimeException("Whoa, io exception: " + e);
}
finally
{
if (bis != null)
{
try
{
bis.close();
}
catch (java.io.IOException ex)
{
}
}
}
}
ganymedeProperties = myGanymedeProperties;
}
return ganymedeProperties.getProperty(configKey);
}
else
{
String property = java.lang.System.getProperty(configKey);
// Starting with Java 7u45, we can't just bring random
// properties from an unsigned JNLP file.
//
// See if the serverhost and registryport information was
// slipped to us through the
// arlut/csd/ganymede/common/build.properties resource
// file in our Jar file.
if (property == null)
{
if (configKey.equals("ganymede.serverhost"))
{
return arlut.csd.ganymede.common.BuildInfo.getServerHost();
}
else if (configKey.equals("ganymede.registryPort"))
{
return arlut.csd.ganymede.common.BuildInfo.getServerPort();
}
}
return property;
}
}
}
/**
* <p>Returns a configuration Integer from a property file or applet
* parameter element, depending on whether the Ganymede client is
* being run as an application or as an applet.</p>
*
* <p>If glogin is being run as an application, the static variable
* WeAreApplet must be set to false, and properties_file should be
* set to point to the Ganymede property file on disk before
* getConfigInteger() is called.</p>
*
* <p>If glogin is being run as an applet, the static variable my_login
* must be set to point to the singleton glogin object before
* getConfigInteger() is called.</p>
*
* @throws NumberFormatException if the config value returned for
* configKey is not a number.
*/
static public Integer getConfigInteger(String configKey)
{
return java.lang.Integer.parseInt(getConfigString(configKey));
}
/**
* <p>Returns a configuration boolean from a property file or applet
* parameter element, depending on whether the Ganymede client is
* being run as an application or as an applet.</p>
*
* <p>If glogin is being run as an application, the static variable
* WeAreApplet must be set to false, and properties_file should be
* set to point to the Ganymede property file on disk before
* getConfigBoolean() is called.</p>
*
* <p>If glogin is being run as an applet, the static variable my_login
* must be set to point to the singleton glogin object before
* getConfigBoolean() is called.</p>
*
* @return defaultValue if there is no property or applet parameter
* matching configKey, else returns true if the property/parameter
* for configKey is equal to "true".
*/
static public boolean getConfigBoolean(String configKey, boolean defaultValue)
{
String val = getConfigString(configKey);
if (val == null)
{
return defaultValue;
}
else
{
if (val.equals("true"))
{
return true;
}
else
{
return false;
}
}
}
public glogin()
{
// we'll offer to send any uncaught exceptions to the server,
// rather than sending it to stderr.
//
// although, before we login we will send it to stderr.
ClientExceptionHandler handler = new ClientExceptionHandler();
try
{
Thread.setDefaultUncaughtExceptionHandler(handler);
}
catch (SecurityException ex)
{
// running in a browser, presumably.. not much we can do about
// it.
}
}
/**
* Standard applet initialization method.
*/
public void init()
{
if (debug)
{
System.out.println("init in glogin");
}
// Look up our saved look and feel, if any
gclient.sizer.restoreLookAndFeel();
// Retrieve the ganymede logo using the appropriate method
ganymede_logo = PackageResources.getImageResource(this, "ganymede.jpg", getClass());
ganymede_ssl_logo = PackageResources.getImageResource(this, "ssl_ganymede.jpg", getClass());
if (WeAreApplet)
{
my_glogin = this;
my_frame = new JFrame(); // we need an invisible frame to attach pop-up dialogs to
}
serverhost = getConfigString("ganymede.serverhost");
try
{
registryPort = getConfigInteger("ganymede.registryPort");
}
catch (NumberFormatException ex)
{
registryPort = registryPortProperty; // default
}
server_url = "rmi://" + serverhost + ":" + registryPort + "/ganymede.server";
getContentPane().setLayout(new BorderLayout());
getContentPane().add("Center", createLoginPanel());
// The Login GUI has been set up. Now the server connection needs
// to be properly established.
my_client = new ClientBase(server_url, this);
/* Spawn a thread to get connected to the server, using the
* ClientBase we just created */
my_thread.setPriority(Thread.NORM_PRIORITY);
my_thread.start();
}
public JPanel createLoginPanel()
{
JPanel loginBox = new JPanel();
loginBox.setBorder(BorderFactory.createEtchedBorder());
gbl = new GridBagLayout();
gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.gridheight = 1;
loginBox.setLayout(gbl);
image = new JLabel(new ImageIcon(ganymede_logo));
image.setOpaque(true);
image.setBackground(Color.black);
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weighty = 1.0;
gbc.weightx = 1.0;
gbl.setConstraints(image, gbc);
loginBox.add(image);
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BorderLayout());
// "Ganymede Server on:"
JLabel label = new JLabel(ts.l("createLoginPanel.server_label1"));
labelPanel.add("North", label);
// "{0}, port {1,number,#}"
JLabel hostLabel = new JLabel(ts.l("createLoginPanel.server_label2",
serverhost,
Integer.valueOf(registryPort)));
Font x = new Font("Courier", Font.ITALIC, 14);
hostLabel.setFont(x);
hostLabel.setForeground(Color.black);
labelPanel.add("South", hostLabel);
gbc.insets = new Insets(1,1,0,0);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weighty = 0.0;
gbc.weightx = 1.0;
gbc.gridy = 1;
gbl.setConstraints(labelPanel, gbc);
loginBox.add(labelPanel);
// the username and passwd fields here won't have their
// callback set with addTextListener().. instead, we'll
// trap the login/quit buttons, and query these
// fields when we process the buttons.
gbc.ipady = 4;
// "Username:"
JLabel userL = new JLabel(ts.l("createLoginPanel.username"));
gbc.fill = GridBagConstraints.NONE;
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 1;
gbc.weightx = 0.0;
gbl.setConstraints(userL, gbc);
loginBox.add(userL);
username = new JTextField(15);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridx = 1;
gbc.weightx = 1.0;
gbl.setConstraints(username, gbc);
username.setEnabled(false);
loginBox.add(username);
// "Password:"
JLabel passL = new JLabel(ts.l("createLoginPanel.password"));
gbc.fill = GridBagConstraints.NONE;
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = 1;
gbc.weightx = 0.0;
gbl.setConstraints(passL, gbc);
loginBox.add(passL);
passwd = new JPasswordField(15);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridx = 1;
gbc.weightx = 1.0;
gbl.setConstraints(passwd, gbc);
passwd.setEnabled(false);
loginBox.add(passwd);
// if we remember the last username that they logged in with,
// pre-set that and focus the password field.
String pre_username = gclient.prefs.get("login_user:" + serverhost, null);
if (pre_username == null || pre_username.equals(""))
{
gclient.prefs.get("login_user", null);
}
if (pre_username != null)
{
username.setText(pre_username);
passwd.requestFocus();
}
gbc.ipady = 0;
// "Quit"
_quitButton = new JButton(ts.l("createLoginPanel.quitButton"));
// "Connecting... {0}"
connector = new JButton(ts.l("global.connecting_text",
Character.valueOf(spinAry[spindex])));
connector.setOpaque(true);
connector.addActionListener(this);
JPanel buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add("Center", connector);
if (!WeAreApplet)
{
buttonPanel.add("East", _quitButton);
}
gbc.gridx = 0;
gbc.gridy = 4;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.BOTH;
gbl.setConstraints(buttonPanel, gbc);
loginBox.add(buttonPanel);
passwd.addActionListener(this);
username.addActionListener(this);
_quitButton.addActionListener(this);
return loginBox;
}
/**
* This will be executed in the thread that tries to connect to the
* server. The thread will terminate after a connection to the
* server has been made.
*/
public void run()
{
int try_number = 0;
/* -- */
if (connecting.set(true))
{
return; // we already have a thread running
}
try
{
while (!connected.isSet())
{
if (try_number++ > 20)
{
break;
}
try
{
EventQueue.invokeAndWait(new Runnable()
{
public void run()
{
// "Connecting... {0}"
connector.setText(ts.l("global.connecting_text",
Character.valueOf(spinAry[spindex])));
}
});
}
catch (Exception ex)
{
ex.printStackTrace();
}
try
{
my_client.connect(); // exceptions ahoy!
connected.set(true);
break;
}
catch (Throwable ex)
{
connectError = ex.getMessage();
if (debug)
{
ex.printStackTrace();
}
}
try
{
spindex++;
if (spindex >= spinAry.length)
{
spindex = 0;
}
// Wait for 1/4 sec before retrying to connect to server
Thread.sleep(250);
}
catch (InterruptedException e)
{
}
}
if (connected.isSet())
{
if (my_client.isSSLEnabled() && !ssl)
{
ssl = true;
image.setIcon(new ImageIcon(ganymede_ssl_logo));
}
EventQueue.invokeLater(new Runnable() {
public void run() {
if (ssl)
{
// "Login to server"
connector.setText(ts.l("run.login_ssl"));
}
else
{
// "Login to server (NO SSL)"
connector.setText(ts.l("run.login_nossl"));
}
enableButtons(true);
connector.paintImmediately(connector.getVisibleRect());
setNormalCursor();
username.setEnabled(true);
passwd.setEnabled(true);
username.paintImmediately(username.getVisibleRect());
passwd.paintImmediately(passwd.getVisibleRect());
String pre_username = username.getText();
if (pre_username != null && !pre_username.equals(""))
{
passwd.requestFocus();
}
else
{
username.requestFocus();
}
invalidate();
validate();
}
});
}
else
{
// "Login Error"
// "Couldn''t locate Ganymede server. Perhaps it is down?\n\n{0}"
// "OK"
new StringDialog(my_frame,
ts.l("run.login_error"),
ts.l("run.login_error_text", connectError),
ts.l("global.ok"),
null,
getErrorImage(), StandardDialog.ModalityType.DOCUMENT_MODAL).showDialog();
EventQueue.invokeLater(new Runnable()
{
public void run()
{
// "Connect"
connector.setText(ts.l("global.connect_text"));
username.setEnabled(false);
passwd.setEnabled(false);
username.requestFocus();
invalidate();
validate();
}
});
}
}
finally
{
connecting.set(false);
}
}
/**
* <p>Logout from the server.</p>
*
* <p>This is called from the gclient.</p>
*/
public void logout()
{
this.logout(false);
}
/**
* <p>Logout from the server.</p>
*
* <p>This is called from the gclient.</p>
*/
public void logout(boolean andQuit)
{
try
{
my_client.disconnect();
}
catch (Exception ex)
{
if (glogin.g_client != null)
{
glogin.g_client.processException(ex);
}
else
{
ex.printStackTrace();
}
}
finally
{
// clean everything up on the gui thread
final boolean myAndQuit = andQuit;
try
{
EventQueue.invokeLater(new Runnable() {
public void run() {
gclient x = null;
synchronized (glogin.class)
{
if (glogin.g_client != null)
{
x = glogin.g_client;
glogin.g_client = null;
}
}
if (x != null)
{
x.setVisible(false);
x.dispose();
x.cleanUp();
}
if (myAndQuit)
{
System.exit(0);
}
}
});
}
catch (NullPointerException ex)
{
}
if (!andQuit)
{
showLoginBox();
enableButtons(true);
}
}
}
/**
* If the applet is no longer visible on the page, we exit.
*/
public void destroy()
{
logout();
}
/**
* Reports our username to gclient
*/
public String getUserName()
{
return my_username;
}
public void enableButtons(boolean enabled)
{
connector.setEnabled(enabled);
_quitButton.setEnabled(enabled);
}
/**
* <p>Set the cursor to a wait cursor (usually a watch.)</p>
*/
public void setWaitCursor()
{
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
/**
* <p>Set the cursor to the normal cursor (usually a pointer).</p>
*
* <p>This is dependent on the operating system.</p>
*/
public void setNormalCursor()
{
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
/**
* <p>Handle button clicks, and enter being hit in the password
* field.</p>
*/
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == username)
{
passwd.requestFocus();
}
else if (e.getSource() == passwd)
{
connector.doClick();
}
else if (e.getSource() == connector)
{
setWaitCursor();
try
{
if (!my_client.isConnected())
{
if (connecting.isSet())
{
return; // our connection thread is still trying to connect
}
else
{
// looks like the ClientBase object lost connection to
// the RMI server.. let's try to re-acquire.
Thread reconnectThread = new Thread(this);
reconnectThread.setPriority(Thread.NORM_PRIORITY);
reconnectThread.start();
return;
}
}
String uname = username.getText().trim();
String pword = new String(passwd.getPassword());
my_passwd = pword;
active_passwd = pword;
my_session = null;
try
{
my_session = my_client.login(uname, pword);
}
catch (Exception ex)
{
// "Couldn''t log into server: \n{0}"
new JErrorDialog(my_frame,
ts.l("actionPerformed.login_failure", ex.getMessage()),
getErrorImage(), StandardDialog.ModalityType.DOCUMENT_MODAL);
enableButtons(true);
}
if (my_session != null)
{
// we need to look up our real username from the
// server, since we might have been logged in using a
// composite username:persona string.
active_username = uname;
try
{
my_username = my_session.getMyUserName();
}
catch (Exception ex)
{
}
enableButtons(false);
// remember last login name.
gclient.prefs.put("login_user:" + serverhost, active_username);
startSession(my_session);
}
else
{
// This means that the user was not able to log into the server properly.
if (!my_client.isConnected() && !connecting.isSet())
{
// looks like the ClientBase object lost connection to
// the RMI server.. let's try to re-acquire.
Thread reconnectThread = new Thread(this);
reconnectThread.setPriority(Thread.NORM_PRIORITY);
reconnectThread.start();
return;
}
else
{
// We are connected to the server.. bad password?
// re-enable the "Login to server" button so that the
// user can try again.
enableButtons(true);
}
}
}
finally
{
setNormalCursor();
}
}
else if (e.getSource() == _quitButton)
{
System.exit(0);
}
}
/**
* Starts the main Ganymede client.
*/
private void startSession(Session session)
{
// try to get the URL for the help document tree
try
{
glogin.helpBase = session.getHelpBase();
}
catch (Exception ex)
{
ex.printStackTrace();
glogin.helpBase = null;
}
// create the thread in our thread group that the disconnected()
// method will use to knock us down.
deathThread = new DeathWatcherThread();
deathThread.setPriority(Thread.NORM_PRIORITY);
deathThread.start();
// and pop up everything
g_client = new gclient(session, this);
passwd.setText("");
// and let's play hide the salami
hideLoginBox();
// now that we've got the g_client reference DeathWatcherThread
// will need, have the client do its post-setup initialization,
// including perhaps blocking on the persona dialog.
g_client.start();
}
public void hideLoginBox()
{
if (hideLoginWhenApplication)
{
if (!isApplet() && my_frame != null)
{
my_frame.setVisible(false);
}
}
}
public void showLoginBox()
{
if (hideLoginWhenApplication)
{
if (!isApplet() && my_frame != null)
{
my_frame.setVisible(true);
passwd.setText(""); // clear the passwd field when we return
passwd.paintImmediately(passwd.getVisibleRect());
}
}
}
// These are for the ClientListener
/**
* Handle a message from the {@link arlut.csd.ganymede.client.ClientBase ClientBase}
* RMI object.
*/
public void messageReceived(ClientEvent e)
{
if (debug)
{
System.out.println(e.getMessage());
}
// constructing a JErrorDialog causes it to be shown.
if (e.getType() == ClientMessage.ERROR)
{
new JErrorDialog(my_frame, e.getMessage(), getErrorImage(), StandardDialog.ModalityType.DOCUMENT_MODAL);
}
else if (e.getType() == ClientMessage.BADCREDS)
{
passwd.setText("");
passwd.requestFocus();
return;
}
else if (e.getType() == ClientMessage.BUILDSTATUS)
{
if (g_client != null)
{
g_client.setBuildStatus(e.getMessage());
}
}
else if (e.getType() == ClientMessage.LOGIN ||
e.getType() == ClientMessage.LOGOUT ||
e.getType() == ClientMessage.COMMITNOTIFY ||
e.getType() == ClientMessage.ABORTNOTIFY)
{
if (g_client != null)
{
g_client.setStatus(e.getMessage());
}
}
else if (e.getType() == ClientMessage.LOGINCOUNT)
{
if (g_client != null)
{
g_client.setLoginCount(Integer.valueOf(e.getMessage()).intValue());
}
else
{
this.initialLoginCount = Integer.valueOf(e.getMessage()).intValue();
}
}
else if (e.getType() == ClientMessage.SOFTTIMEOUT)
{
if (g_client != null)
{
g_client.softTimeout();
}
}
}
public int getInitialLoginCount()
{
return this.initialLoginCount;
}
/**
* Handle a forced disconnect message from the
* {@link arlut.csd.ganymede.client.ClientBase ClientBase} RMI object.
*/
public void disconnected(ClientEvent e)
{
try
{
deathThread.die(e.getMessage());
}
catch (NullPointerException ex)
{
}
}
/**
* <p>Loads and returns the error Image for use in client
* dialogs.</p>
*
* <p>Once the image is loaded, it is cached for future calls to
* getErrorImage().</p>
*/
public final Image getErrorImage()
{
if (errorImage == null)
{
errorImage = PackageResources.getImageResource(this, "error.gif", getClass());
}
return errorImage;
}
}
/*------------------------------------------------------------------------------
class
DeathWatcherThread
------------------------------------------------------------------------------*/
/**
* <p>Client-side background thread to handle force disconnect
* commands from the server. We need this thread because jdk 1.2 has
* a bug where RMI callbacks are not privileged to interact with the
* Swing thread. By creating a thread to handle forced logouts
* ourselves, we can have an RMI callback pass a message to this
* thread (which has local privileges), which can then throw up a
* dialog explaining about being disconnected, etc.</p>
*
* <p>When run, this thread waits for die() to be called, whereupon it
* creates an {@link arlut.csd.ganymede.client.ExitThread ExitThread}
* to actually shut down the client.</p>
*
* @author Jonathan Abbey
*/
class DeathWatcherThread extends Thread {
/**
* TranslationService object for handling string localization in the
* Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.DeathWatcherThread");
String message = null;
/* -- */
public DeathWatcherThread()
{
}
public synchronized void run()
{
while (message == null)
{
try
{
wait();
}
catch (InterruptedException ex)
{
if (message == null)
{
return;
}
}
}
// if the user was stuck at the modal persona selection dialog,
// close it so that we can put our own error message up.
try
{
glogin.g_client.getPersonaDialog().changedOK = true;
glogin.g_client.getPersonaDialog().setHidden(true);
}
catch (NullPointerException ex)
{
}
ExitThread exitThread = new ExitThread(message);
// start up the death timer, which will close all our active
// windows in a bit.. we hold off on just doing it now to not
// startle the user too much.
exitThread.setPriority(Thread.NORM_PRIORITY);
exitThread.start();
// throw up a modal dialog to get the user's attention
// "The Ganymede Server is disconnecting us:\n\n{0} "
new JErrorDialog(glogin.g_client,
ts.l("run.kicked_off", message),
glogin.g_client.getErrorImage(), StandardDialog.ModalityType.DOCUMENT_MODAL);
// if we get here, the dialog has been put down
exitThread.dieNow();
}
/**
* This method causes the DeathWatcherThread to kick off the
* end-of-the-world process.
*/
public synchronized void die(String message)
{
this.message = message;
notify();
}
}
/*------------------------------------------------------------------------------
class
ExitThread
------------------------------------------------------------------------------*/
/**
* <p>Client-side self-destruction thread. This thread will be
* created and run when the server sends the client's {@link
* arlut.csd.ganymede.client.ClientBase ClientBase} a forced
* disconnect RMI call. When run, this thread starts a 30 second
* timer, while the {@link
* arlut.csd.ganymede.client.DeathWatcherThread DeathWatcherThread}
* shows a dialog to the user, explaining the disconnect. The user
* can click ok on that dialog, causing this thread's dieNow() method
* to terminate the timer. In any case, when the timer counts down to
* zero, the glogin's logout() method will be called, and the client's
* main window will be shutdown.</p>
*
* @author Jonathan Abbey
*/
class ExitThread extends Thread {
final static boolean debug = false;
String message;
boolean dieNow = false;
/* -- */
public ExitThread(String message)
{
this.message = message;
}
public synchronized void run()
{
if (debug)
{
System.out.println("ExitThread: running");
}
int i = 30;
if (debug)
{
System.out.print("System shutting down in 30 seconds");
}
try
{
while (!dieNow && i > 0)
{
sleep(1000);
if (debug)
{
System.out.print(".");
}
i--;
}
}
catch (InterruptedException ie)
{
if (debug)
{
System.out.println("glogin: Interupted trying to sleep and quit: " + ie);
}
}
if (debug)
{
System.out.println("\nGanymede disconnected: " + message);
}
glogin.my_glogin.logout();
}
public void dieNow()
{
this.dieNow = true;
}
}
/*------------------------------------------------------------------------------
class
gloginFrame
------------------------------------------------------------------------------*/
/**
* JFrame subclass which is used to hold the {@link
* arlut.csd.ganymede.client.glogin glogin} applet when the Ganymede
* client is run as an application rather than an applet.
*/
class gloginFrame extends JFrame {
static final boolean debug = false;
glogin client;
/* -- */
public gloginFrame(String title, glogin client)
{
super(title);
this.client = client;
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
}
protected void processWindowEvent(WindowEvent e)
{
if (e.getID() == WindowEvent.WINDOW_CLOSING)
{
if (debug)
{
System.out.println("Window closing");
}
if (client._quitButton.isEnabled())
{
if (debug)
{
System.out.println("It's ok to log out.");
}
System.exit(0);
super.processWindowEvent(e);
}
else if (debug)
{
System.out.println("No log out!");
}
}
else
{
super.processWindowEvent(e);
}
}
}