10.7. The Messenger Applet

Finally, we have the various entries and supporting classes behind us and we can now turn our attention to the Messenger class that ties the application together. Let's start by looking at the skeleton of the Messenger class:

public class Messenger extends Applet
  implements ActionListener
{
  //... user interface variables go here

  private JavaSpace space;
  private TransactionManager mgr;
  private String username;
  private String password;

  public void init() {
    super.init();
    showStatus("Getting space");
    space = SpaceAccessor.getSpace();
    mgr = TransactionManagerAccessor.getManager();

    login = new Login(this);
  }

  //... method to set up messenger console interface goes here

  //... actionPerformed method (for console events) goes here

  //... inner class MouseHandler (for console events) goes here

  //... login callback method goes here

  //... inner class Listener goes here

  public JavaSpace space() {
    return space;
  }

  public TransactionManager mgr() {
    return mgr;
  }
}

The Messenger is an applet that primarily serves as the basis for the user interface. Our skeleton shows the applet's init method, which first gains access to a space and a transaction manager and assigns them to two class variables. These variables are available through two public methods (space and mgr), which are shown at the bottom of the skeleton and used, as we have already seen, by our helper objects to gain access to a space or a transaction manager.

The applet then instantiates a Login object, which is where the fun begins. The Login object is responsible for managing the user login. The Login object is also responsible for creating an account and its associated data structures (if the user is new), starting a user session, and letting the Messenger know the user has been authenticated by calling the Messenger's loginCallback method. Let's see how.

10.7.1. Logging in via the Login Object

When the Messenger instantiates a Login object, it passes itself as a parameter. The Login object brings up the login window shown in Figure 10.1. We will skip the full implementation details of the user interface, but it's important to examine the actionPerformed method that's called whenever the user presses the “Login” button or hits “Enter” in the password field:

public void actionPerformed(ActionEvent event) {
  String username = usernameTextField.getText();
  String password = passwordTextField.getText();

  if (username.equals("") || password.equals("")) {
     setStatus("Enter both username & password.");
     return;
  }

  Account userAccount =
    new Account(messenger, username, password);
  try {
     userAccount.validate();
  } catch (InvalidPasswordException e) {
    setStatus("Invalid password.");
    return;
  } catch (UnknownUserException e1) {
    try {
       userAccount.create();
       messenger.showStatus("Created new account for " +
        username + ".");
  } catch (Exception e2) {
     messenger.showStatus(
      "Could not create account for " +
      username + ".");
     return;
  }
}

Session userSession = new Session(space, username);
try {
   userSession.start();
} catch (Exception e) {
   messenger.showStatus("Could not create session for " +
    username + ".");
   return;
}

dispose();
messenger.loginCallback(username, password);
}

This method first ensures that both a username and password have been provided. If not, an error message is printed, the method returns, and the user is free to try again. Otherwise, the method instantiates an Account object (passing it a handle to the messenger applet and the supplied username and password) and invokes its validate method. If validate throws an InvalidPasswordException, the method displays an error message and returns, and the user may try again. If validate throws an UnknownUserException, the account's create method is called to create a new user account—writing an account entry, friends list entry, and channel distributed data structure into the space.

After create completes, or if validate completes without exceptions, a new session is started for the user. As we saw in Section 10.4.1, this results in a SessionEntry being written into the space, indicating that the user is logged in.

Finally, the method destroys the login window by calling dispose, and notifies the messenger applet by calling its loginCallback method.

10.7.2. The loginCallback Method

The loginCallback method is defined in Messenger and acts to notify the messenger when the user has logged in. This callback also does some important work; namely, it sets up the messenger console shown in Figure 10.1, and adds listeners that respond to graphical events (such as clicking on a name to chat, or pressing the “Add” button to add a friend). The callback also takes care of setting up a friends list and ensuring that it's continually monitored and displayed to the console. In addition, the callback starts a listener object that will be notified whenever new messages are appended to the user's channel, so that they can be displayed. Finally, the callback creates a message dispatcher object that is used to display messages in the appropriate message windows.

This is an important method; here is its definition:

public void loginCallback(String username, String password) {
  this.username = username;
  this.password = password;

  consoleSetup();

  friendsList = new FriendsList(this, username);
  friendsList.monitor(friendsDisplay);

  try {
     Listener listener = new Listener();
     Index template = new Index("tail", username);
     space.notify(template, null, listener,
       Lease.FOREVER, null);
  } catch (RemoteException e) {
     e.printStackTrace();
  } catch (TransactionException e) {
     e.printStackTrace();
  }
  showStatus(username + " logged in.");

  dispatch = new MessageDispatcher(this);
}

The callback is passed the authenticated user's username and password and these are assigned to the username and password fields of the Messenger object. The method then invokes consoleSetup to set up the user interface for the messenger console shown in Figure 10.1. Part of the console setup involves adding event listeners that will be called when the user presses the “Add” button, presses “Enter” in the text field, or clicks on a name in the online friends display area. We will come back to these actions shortly.

The messenger also needs to continually monitor the friends who are online and display them to the friends console. To do this, the callback constructs a FriendsList object and invokes its monitor method. As we discussed in Section 10.5.1, monitor starts a FriendsListMonitor thread that repeatedly reads a user's friends list entry and prints its contents to the console.

Next, loginCallback sets up a mechanism for retrieving and displaying the new messages in the user's channel. In this example, we use a reactive style of finding out about new messages by making use of notify. This approach contrasts with the channels we built in Chapter 5, in which a thread looped and repeatedly performed a read on the next message in the channel.

To use notify, the loginCallback method sets up a listener object that will be informed whenever the tail entry of the channel is updated in the space, which indicates that messages have been appended. This object, assigned to listener, is instantiated from an inner class of Messenger, called Listener. The loginCallback method then creates a template that will match the user's tail entry. With a template and listener in hand, the method calls notify on the space to register interest in being informed whenever the tail of the user's channel is updated. The first parameter to notify is the template of interest, the second indicates a null transaction, the third specifies that the listener object should be informed of matching entries, the fourth means the notify registration will be remembered indefinitely by the space, and the fifth indicates that there is no handback object. So as a result of this registration, whenever the tail entry is written to the space, the listener's notify method will be called; we'll get to the details of the Listener class in Section 10.7.6.

Finally, loginCallback instantiates a MessageDispatcher object and assigns it to the dispatch field. We'll see shortly that, when new messages are removed from the user's channel, the dispatcher comes into play to direct chat messages to the appropriate chat window on the user's screen.

10.7.3. Adding to the Friends List

Once the user is logged in, the messenger enables the user to manage a friends list and keep track of which friends are online. Let's see how. Recall that we said the consoleSetup method creates the messenger console interface and adds event listeners to respond to events. In particular, when the “Add” button is clicked or “Enter” is pressed in the text field, the messenger's actionPerformed method is called:

public void actionPerformed(ActionEvent event) {
  String friend = friendTextField.getText();
  Object object = event.getSource();

  if ((object == addButton) || (object == friendTextField)) {
     if (friend.equals("")) {
        showStatus("Enter a friend's username.");
     } else {
       showStatus("Adding friend " + friend + " to list.");
       friendTextField.setText("");
       friendsList.add(friend);
     }
  }
}

This method first makes sure that a name has been typed into the text field, and if so, calls the add method on the messenger's friendsList to add the friend. Recall that this causes the user's FriendsListEntry to be retrieved from the space and a new name to be added to the entry's vector of names. The next time the friendsList monitor thread retrieves this entry, the new name will be included in the set of names that are checked for their online status.

10.7.4. Chatting with Friends

When the user clicks on the name in the friends list to initiate a chat session, the mouseReleased method of the messenger's MouseHandler inner class is called:

class MouseHandler extends java.awt.event.MouseAdapter {
  public void mouseReleased(java.awt.event.MouseEvent event)
  {
    Object object = event.getSource();
    if (object == friendsDisplay) {
       try {
          dispatch.handle(username,
             friendsDisplay.getSelectedItem(),
             username, new Date(), "");
       } catch (Exception e) {
         System.err.println(
           "Couldn't display message window");
         e.printStackTrace();
       }
    }
  }
}

The mouseReleased method calls the handle method of the messenger dispatcher. It passes several parameters: the username of the person initiating contact, the name of the friend we clicked on, and the author, date, and content (which in this case is empty) of the message.

If there isn't already a window open on the screen for chatting with this friend, the dispatcher is responsible for setting up a new window. Here's what the MessageDispatcher code looks like:

public class MessageDispatcher {
  private Messenger messenger;
  private Hashtable frames = new Hashtable();

  public MessageDispatcher(Messenger messenger) {
    this.messenger = messenger;
  }

  public void handle(String username, String friend,
    String author, Date time,  String content)
  {
    MessageFrame frame = (MessageFrame)frames.get(friend);
    if (frame == null || !frame.isShowing()) {
       frame = new MessageFrame(messenger, username, friend);
       frames.put(friend, frame);
    }
    frame.addMessage(author, time, content);
  }
}

A message dispatcher is essentially a hashtable that maintains the chat windows currently displayed for chatting with various friends. These windows are MessageFrame objects; each message frame allows a user to direct messages to, and receive messages from, a particular friend.

When the handle method is called, it looks in the hashtable to see if a message frame for chatting with the given friend already exists. If it does, the addMessage method is called on the frame, to display the author, time, and content of the message. If the frame doesn't yet exist, one is created and displayed (in the form of a chat window as shown in Figure 10.1) and also put into the hashtable.

10.7.5. Sending Messages

Once we have a chat window (a MessageFrame object) open to a friend, we can send messages. The bulk of the code in MessageFrame sets up the user interface. What we are interested in is what happens when the user types a message into a message frame and clicks on the “Send” button or presses the “Enter” key, which is handled by the actionPerformed method:

public void actionPerformed(ActionEvent event) {
  String content = messageTextField.getText();

  if (content.equals("")) {
     setStatus("Enter a message.");
     return;
  }

  messageTextField.setText("");

  // add the message here
  addMessage(username, new Date(), content);
  ChannelMessageEntry message =
    new ChannelMessageEntry(friend, username, content);
  Channel channel = new Channel(messenger, friend);
  channel.append(message);
}

This method first extracts the text from the message text field (making sure it's not empty) and calls its own addMessage method to display the message in the message display area of the local frame. The method then constructs a ChannelMessageEntry that contains the message's recipient, the local user's name, and the content of the message. Next the method instantiates a Channel object representing the friend's communication channel, and calls append to add the message to that channel.

This is how messages are sent. Now let's see how they are received as they come in on the channel.

10.7.6. Listening for Messages

In Section 10.7.2 we set up a listener object that is notified whenever the tail of the user's communication channel is updated in the space. Let's now take a look at the messenger's inner class Listener, which performs this work:

public class Listener implements RemoteEventListener, Runnable {
  private int last = 0;

  public Listener() throws RemoteException {
    UnicastRemoteObject.exportObject(this);
  }

  public void notify(RemoteEvent ev) {
    Thread temp = new Thread(this);
    temp.start();
  }

  public void run() {
    Channel channel = new Channel(Messenger.this, username);
    Enumeration e = channel.getMessages();
    while (e.hasMoreElements()) {
      ChannelMessageEntry message =
        (ChannelMessageEntry) e.nextElement();
      dispatch.handle(username, message.from,
         message.from, message.time, message.content);
    }
  }
}

You'll recall from Chapter 8 that the methods that handle notify events are expected to return as quickly as possible, in order to prevent the remote space from blocking. Here, the notify method instantiates and starts a new thread that executes the run method, thereby offloading the event processing and returning promptly.

The run method calls getMessages to enumerate all messages currently in the user's channel, and then iterates through them. For each message, the message dispatcher's handle method is called. Note that the second argument passed to handle is the sender of the message, which handle uses to locate a message frame corresponding to that sender. If a message frame exists for that sender, then the message is displayed to that frame, otherwise handle creates a new message frame and opens up a chat window for talking to that sender.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset