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.
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.
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:
-
Document editing
-
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.
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.
Sword will provide authentication, confidentiality, and data integrity.
Security can be implemented in stages, which accumulate:
-
No security.
-
User name and password authentication to the server.
-
Use SSL sockets for confidentiality and integrity.
-
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.
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.
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.
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.
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.
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.