★ wanayoo — archive 1999 http://java.oreilly.com/bite-size/java_0500.htmlNouvelle recherche | Portail wanayoo
Bite-Size Java
O'Reilly HomeO'Reilly Network
ConferencesSoftwareInternational

Arrow Search
Arrow Product List
Arrow Press Room
Resource
Centers

Arrow
Perl
Java
Web & Internet
Open Source
Linux
Unix
Macintosh
Windows
Oracle
Security
Sys/Network Admin
C/C++ Programming
Design & Graphics
Visual Basic
Java 2D Graphics
Special
Interest

Arrow
Ask Tim
Frankly Speaking
Bite-Size Java
Ron's VB Forum
Conferences
Beta Chapters
PalmPilot
Books Online
Specials
Write for Us
Work with Us
O'Reilly
 

Jonathan Knudsen What Are We Going To Do Tonight, Brain?

by Jonathan Knudsen


Like almost everyone else in the computer business, I have a plan for world domination. My plan involves real-time collaborative applications--in particular, a word processor that allows multiple people to work on the same document at the same time. This seems like a no-brainer--very useful, and not too hard to build--but I haven't been able to find any implementations of this idea. If you know of one, please let me know.

In the meantime, I'll use this space to lay out my ideas for this project. I'd be happy to hear any comments on the design. Drop me an email if you have anything to say.

Vision

The purpose of this application, which I'm calling Sword, is to allow multiple people to edit a document at the same time. This could be useful for creating any kind of collaborative document. Rather than sending files around or setting up a version control system, all the authors can work on the same document at the same time. It's even conceivable this would be a nice way to write code with other people.

From the user standpoint, it works like this. You fire up your copy of Sword, a Java 2 Swing application. When it starts up, it looks a lot like a regular word processor. Instead of editing document files on your hard disk, however, this application edits files on a server somewhere. When you open a file, it is sent from the server to your client-side Sword application. When you type to make changes, these changes are sent up to the server to be verified. When other people make changes on the same document, their changes are sent to the server and forwarded to you. Your document will be updated with everyone's changes as you watch. The server keeps track of the changes and updates the document file.

An optional chat window associated with a document allows you to converse with other authors. Alternately, you could all be on a regular voice conference call as you collaborate.

Overview

Sword is a client-server application. The server portion maintains documents and makes them available for editing. It responds to edit messages from the clients and distributes them back out to other clients editing the same document. The client part displays the document on a user's screen and handles communications with the server.

The server offers two essential services:

  1. Document editing
  2. File management
We'll make the server general enough to offer a directory tree hierarchy up for editing. The client will be able to use the server to browse through this tree and choose files for editing.

Edit Arbitration

For text documents, the server must be the referee for the clients editing a document. In most cases, edits made by one client can simply be propagated to the other clients. Sometimes, though, two clients will make changes that affect the same area of the document. In this case, the server needs to allow the first edit it receives and deny the second. Clients only update themselves with server-approved edits. The client who made the denied edit will need to undo its changes and be updated with the server-approved edits.

Security

Sword will provide authentication, confidentiality, and data integrity. Security can be implemented in stages, which accumulate:

  1. No security.
  2. User name and password authentication to the server.
  3. Use SSL sockets for confidentiality and integrity.
  4. Permission-based access to files and directories on the server.
For the first implementation, I'll probably ignore security altogether, but I'll try to design the application so that it's not hard to add security features later.

Server Side Architecture

On the server side, four classes do the bulk of the work:

Service

This class listens for incoming socket connections. For each connection, it spins off a new Server object in its own thread.
Server
This class handles interaction all interaction with a single client. It responds to commands from the client to browse and edit files. It also handles client authentication. Most commands are delegated to other objects, like FileManager and DocumentEditor (which are described next).
FileManager
There is a Server for each client; the Server keeps a reference to a FileManager, used for browsing files on the server. The FileManager is almost like a simple shell. It has a current directory and methods like pwd(), cd(), and ls(). In later development, FileManager can be augmented to maintain meta-information about files, like permissions and file types.
DocumentEditor
A DocumentEditor responds to messages from clients to edit a single document. On the server side, there is exactly one DocumentEditor per requested file. If two clients want to edit the same file, they'll end up talking to the same DocumentEditor, even though they'll have different Servers. Because a client can have more than one file open for editing, a Server keeps a list of DocumentEditors representing the currently open documents.

Client Side Architecture

The client is considerably simpler. A single class, Client, handles interactions with the server and the visual editor. It sends edits entered by the user up to the server. Client also listens for updates from the server and makes the appropriate changes to the visual editor.

The rest of the client application is standard Swing plumbing.

A Little Bit of Implementation

Let's take a look at some sketchy classes that implement some of these ideas. The first class to examine is Service. It's very straightforward--it listens on a port and spins off Server instances for connections:


import java.io.*;
import java.net.*;

/**
 * Service accepts connections on a port and spins
 * out new Server instances.
 */
public class Service {
     public static void main(String[] args) throws Exception {
          int port = 7090;
          if (args.length › 0) port = Integer.parseInt(args[0]);
    
          System.out.println("Starting up Service on port " + port + ".");
          Service s = new Service(port);
          s.run();
     }

     private int mPort;
     private ServerSocket mServerSocket;
  
     public Service(int port) { mPort = port; }
  
     public void run() {
          try {
              if (mServerSocket == null)
               mServerSocket = new ServerSocket(mPort);
             while (true) {
                  Socket client = mServerSocket.accept();
                  // Spin off a new server.
                  BufferedReader in = new BufferedReader(
                          new InputStreamReader(client.getInputStream()));
                  PrintWriter out = new PrintWriter(client.getOutputStream(), true);
                  System.out.println("Received client connection.");
                  Thread t = new Thread(new Server(in, out));
                  t.start();
             }
          }
          catch (IOException ioe) {}
     }
  
     public void stop() {
          if (mServerSocket != null) {
             try { mServerSocket.close(); }
             catch (IOException ioe) {}
          }
     }
}

Service includes a main() method, so you can run it from the command line. You can specify a port number if you wish; otherwise, a default port, 7090, will be used.

The Server class handles client connections. One Server instance exists per connected client.

import java.io.*;
import java.util.*;

/**
 * A Server runs a socket connection to a client.
 */
public class Server
        implements Runnable {
     private BufferedReader mIn;
     private PrintWriter mOut;
  
     public Server(BufferedReader in, PrintWriter out) {
          mIn = in;
          mOut = out;
     }
  
     public void run() {
          try {
             // Server state.
             User user = null;
             FileManager fileManager = null;
             Map documentEditors = new HashMap();
      
             String line;
             while ((line = mIn.readLine()) != null) {
                  mOut.println("500 Not implemented");
                  // Parse the line.
                  // For authentication, take the given user name
                  //   and password and authenticate to create a
                  //   User object. Use the User to create a new
                  //   FileManager based on the User's home directory.
                  // When a file edit is requested, create a new
                  //   DocumentEditor. DocumentEditor's factory
                  //   method insures that only one DocumentEditor
                  //   exists per document.
                  // Pass commands to FileManager or DocumentEditor
                  //   as appropriate.
             }
          }
          catch (IOException ioe) {}
     }
  
     public class ParsedLine {
          private String mCommand, mOptions, mArgument;
          private boolean mFlattenCase = true;
    
          public ParsedLine(String line) {
               line = line.toLowerCase();
               StringTokenizer st = new StringTokenizer(line, " ");
               mCommand = st.nextToken();
               mArgument = st.nextToken();
               if (st.hasMoreTokens()) {
                 mOptions = mArgument;
                 mArgument = st.nextToken();
               }
          }
    
          public String getCommand() { return mCommand; }
          public String getOptions() { return mOptions; }
          public String getArgument() { return mArgument; }
     }
}

The crux of the Server class is the run() method. As each line is read from the client, it is parsed and the client command is routed to an appropriate object. For example, the client might send file navigation commands--these would get delegated to a FileManager object. Document editing commands are delegated to a DocumentEditor.

Server also handles authentication. If the client attempts to authenticate (or login), Server tries to create a User object. I haven't shown User here; just think of it as a class that represents a user and his or her home directory. User also has a static getInstance() method that checks passwords (perhaps by looking in a database) and returns new User objects. Once the client has been authenticated, Server creates a new FileManager using the home directory of the User.

Subsequent client requests to move around in the directory tree, to create or destroy directories, or other file operations, are passed to the FileManager instance. Whenever the client wants to edit a file, a new DocumentEditor instance is obtained. Since the client can edit multiple documents, Server keeps a collection of DocumentEditor references, one per open document on the client side. Different DocumentEditors are represented by different names. Each time the client sends an editing command, it must specify the name of a DocumentEditor so the Server knows where to send the command.

The Server class presented above also includes a inner subclass called ParsedLine, which should be handy for parsing commands and delegating them.

Lots of Exercise for the Reader

Much remains to be done. FileManager and DocumentEditor need to be written, and of course we haven't even talked about the client-side stuff yet. Even before writing more code, however, we should probably continue with the design by specifying the protocol between the client and server and the format of documents themselves. I envision a line-oriented text protocol, like POP3 or SMTP, because it makes debugging pretty easy. On an SSL socket, it should be just as hard to read as anything else. It's conceivable you could use RMI instead, although this would force clients to be programmed in Java. The text-based protocol makes things more open-ended.

I believe this application represents the next generation of software--truly collaborative software. Sure, we've got some collaborative stuff now, like newsgroups and dynamically updated web pages and CVS repositories, but it's not quite the same as multiple users editing documents in real time. New collaborative tools will enable the full power of the Internet: groups forming and dissolving almost transparently to get a particular job done.

That's All He Wrote

In other news, this will be my last Bite-Size Java column, at least for the near term. I'm moving to a new job at LearningPatterns.com, and my wife is due to have our fourth baby in August, so I'm trying to make things a little less complicated for myself. Thanks for your support over the last three years. It's been a lot of fun.

Download the source code.


Jonathan Knudsen is an author and developer at O'Reilly & Associates. He is the author of The Unofficial Guide to LEGO® MINDSTORMSc Robots, Java 2D Graphics, and Java Cryptography.

O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2000, O'Reilly & Associates, Inc.
webmaster@oreilly.com