Server-Side Java: Extending Web Servers Through Java
Part 1: Client-Server Programming With Java Applications
By Rich Kadel
Send comments and questions about this article to View Source.
Click here for printer-friendly version
Java has matured to the point where it should now be considered for
serious application development. To be honest, I probably wouldn't
have said that a year ago, about the time I started trying some serious
things. But Java and I have matured together, at about the same pace. Now
that I do serious things with Java, I have less of a fear that Java might
not do what I want it to do.
Don't get me wrong. I know there's still lots of room for improvement.
But I know where the boundaries are, I know how to avoid stepping over
them, and I constantly see those boundaries expanding with each new Java
implementation. Java is actually quite powerful, and it has a number of
advantages over other languages that, in my opinion, make Java a better
choice for almost all programming tasks:
- Java has a powerful feature set, including built-in threads, exceptions,
security, and an excellent object model.
- Java is actually quite simple to use, considering its feature set.
Although some might disagree, I think Java is much simpler than C++.
- Java source code and compiled class files work, automatically,
across a multitude of platforms. All of my applications run, without modification,
on Windows, Mac OS, and UNIX systems.
- Java is inherently network-aware, with objects that map to sockets
(TCP/IP network connections), server sockets, URLs, and other useful networking
capabilities.
I have written client/server applications in Java, C++, C, and other
languages for more than 10 years now. I have to say that, when I started
my first Java client/server application, I was stunned at how easy it was
to develop; it's been even easier to maintain and extend. This is due,
primarily, to the fundamental features of the Java language itself, in
that Java is object-oriented, multithreaded, and network-aware.
With the data streaming interfaces we have now in Java 1.0.2, and the
object serialization we will have in future Java 1.1 implementations, Java
is an excellent choice for developing client-server applications. Java
is often thought of as the client-side language for developing "thin-client"
applications. But if Java is also used to develop the server, the same
objects, and therefore the same source code and algorithms, can be used
on both sides of the client/server connection. I'll spend the rest
of the article going into each of these advantages in detail.
Developing server-side software for the Web
There used to be few choices when it came to developing for the Web.
HTML was about it at first. Then the Common
Gateway Interface (CGI) came along, but the language choices were still
usually limited to PERL or C, for lack of examples or tools in other languages.
Then came the server application programming interfaces, or APIs, such
as the Netscape
Server Application Programming Interface (NSAPI), and more client-side
(browser-side) programming choices including Java and JavaScript as well
as server-side
JavaScript. Now the choices are much broader, and they include Java
on both the client and the server.
This is the first of a two-part article on developing server-side programs
in Java. If you're not sold on the idea, read on. I will first talk about
why Java is an excellent choice for server-side programming. Then
I'll get into some of the nuts-and-bolts of Java development on the server,
using Java applications. In part two, I'll dive head-first into Netscape
Server Applets (Web server-embedded Java programs) and LiveConnect for
the server, which is an important part of the new features of Netscape
Enterprise Server 3.0, now available in beta.
This technology is very powerful, even though it's still very new, and
it allows you to actually extend the server using Java applets that
are web server-aware.
Difference between Java Applets and Java Applications
Java first gained its wide-spread popularity from several simple, yet
interesting, Java applets running under Netscape Navigator 2.0. These examples
showed users that Web pages could actually be very dynamic, and even interactive,
using Java. But most of these initial applets demonstrated little more
than spinning heads, moving text, and 3D molecules. Because of the strict
security constraints placed on Java applets, these examples were cute,
but not very useful. (This is changing with the release of Java 1.1 and
Netscape Communicator; see the sidebar "Object
Signing to the Rescue.")
I'm sure somebody is going to disagree with me here, but from my point
of view, the typical Java applet has a lot of flash, but very little function.
Without the ability to interact with its environment - for example, by
saving a new file, or communicating information with another program -
a Java applet puts the end-user in a very passive, "Look, but don't
touch" role. Most popular software applications - word processors,
spread sheets, databases - are centered on the "data" and how
the user manipulates the data. Stand-alone Java applets have built-in functions
to download images, sound files, and even some static data. But for the
sake of security (an important, albeit suffocating cause) Java can't, for
example, drop that file into, say, a Microsoft Word document (except through
some really basic, unformatted cut-and-paste), and it can't read an image
you created in Corel Draw 7.
|
Object Signing to the Rescue
The new version of Java, Java 1.1, introduces some new security features.
In particular, applets and the data they produce can be digitally "signed."
Netscape Communicator supports this feature, and Netscape has released
a product called the JAR Packager
to make it easier for developers to sign their applets. User can set up
Communicator to "trust" applets that are signed by a particular
person or company.
Signed applets can then, at the user's option, have access to local
system resources - like the local disk - previously restricted to Java
applets.
One of the more recent concepts, as I understand it, will allow an applet
to save and load signed data. Through the browser, you should be able to
restrict an applet to saving and restoring only its own files. This mechanism
will allow applets like word processors to save documents to a user's hard
disk.
This is not a solution for applications that should be client-server
oriented, and will not help thin clients (like a Network Computer) that
have no local disk device. However, it's definitely a big win for developers
that want to use the applet concept for application distribution, and would
like to save user documents or state files from applets.
|
I'll be the first to admit that my first impressions of Java were not
very positive. Like lots of people, I thought that Java was limited to
these restricted applets. (Boy, was I missing the point!)
Then I found out how to write Java applications. Java applications (as
I use the term) are Java programs that run outside of a browser.
They have the same basic capabilities as a C or C++ program, but run inside
a stand-alone virtual machine (VM), which makes them work with almost any
operating system. (Applets run in a VM that is embedded in the browser.)
A Java application may or may not have a window or GUI. If it does have
a GUI, it creates its own window (or windows) and behave just like all
other windows on your desktop, whether under Windows, Macintosh, or UNIX
Motif.
A Java application can also, of course, run on your UNIX, NT, or other
server. The great thing about these Java applications is that there are
no Java-enforced security constraints! I could actually write an
application that would wipe my entire hard disk, if I wanted to! (Well,
I probably wouldn't want to, but hey, now there were possibilities.) What
I did want to do was to write Java applications that could, for
example, write to and read from the server's hard disk. With this kind
of capability, I could use Java to develop simple (or not-so-simple) database
servers and proxies.
By this time, I was hooked. Java was all the buzz, and I was experiencing
the advantages of Java as a language and development platform daily, if
not hourly. (I outlined some of these advantages at the beginning
of this article; if you wan't more detail, you can find
it at JavaSoft's web site.)
Java is an Excellent Choice for Both Clients and Servers
You could develop a server in any language, and it would be transparent
to the client, as long as the server honored the specified protocol. However,
Java has become my first choice for many reasons. One of the biggest advantages
is that you can use the same or similar classes on both the client and
the server side for reading and writing the "objects" related
to your business problem. Even at the lowest level, Java's Data classes
guarantee that the numbers and strings you send will be translated
correctly even among heterogeneous platforms.
I already referred to the tremendous programming benefits of Java for
applications, but the portability of a server written in Java is a strong
selling point, worth a second look. My company, DTAI,
developed the Interactive
Org Chart as a client/server product. Our original version, developed
for Netscape
AppFoundry, included a very simple, hand-coded database and a tailored
protocol.
We had to be able to distribute our product on several client and
server platforms. That meant that we had to produce servers for both
Windows NT and UNIX. Most programmers dread the thought of writing a C
or C++ application that has to be ported to two or more platforms, each
with completely different programming environments, and system interfaces.
But with Java, we wrote each server only once and it worked perfectly on
both UNIX and NT.
Even if you think you have no multiplatform server requirements
today, take a moment and try to recall the type of hardware and operating
system you or your company were using 5 or 10 years ago. Did it change?
I would venture to say that almost 100 percent of you would have to answer
"Yes." And chances are that the system you develop for in the
next 5 or 10 years will be different from the one you use today.
Taking Advantage of Java's Built-in Network and Data Classes
Another of Java's strengths as a programming environment is that it
is very network-aware. And, in fact, despite all of the other security
restrictions on applets, an applet can open a TCP/IP socket connection,
as long as it is only to the host from which the applet was downloaded.
This is an important point. When a Web page containing an applet is
downloaded to a browser, that applet is not allowed to open a socket connection
to the local system (where the browser is running). Nor is it allowed to
connect to other systems (for example, computers inside the client's firewall).
Therefore, even though it can open a connection back to the Web
server computer, it will not be able to extract any potentially
private data from the client to send back to the server.
Through the socket, the client can both send and receive data. This
permits the client to use the server as a proxy to the server's local resources,
and even to other network resources accessible by the server. Some Java
developers have even implemented a virtual file system for applets. With
such systems, a user can select, open, and save files from an applet, but
the data is actually read from and written to the server's local file system,
and the data is transferred between the client and the server through sockets.
There are several Java classes that make it easy to set up network connections,
using TCP/IP, and socket I/O. Java also has a powerful I/O stream model
that allows you to combine I/O stream classes together - with sockets,
files, or other stream targets - to meet any of several possible configurations.
It's easy to use Java to open a connection to any kind of server program
on another computer and set up buffered data streams for reading and writing
information between the two programs. The following code shows just how
easy that is:
Socket socket = new Socket( "some.host.com", 80 );
DataInputStream instream =
new DataInputStream(
new BufferedInputStream(
socket.getInputStream() ) );
DataOutputStream outstream =
new DataOutputStream(
new BufferedOutputStream(
socket.getOutputStream() ) );
The Socket class only requires two pieces of information - passed to
the "constructor" function above - in order to make its connection:
the target computer's host name and the TCP/IP port number. Unless the
constructor throws an exception (which may happen if, for instance, the
program on the other end doesn't respond), the first line in the above
example will cause a solid connection to be made between this Java process
and another process, possibly on another machine, anywhere on the network.
The example just shown calls only two methods on the Socket object,
getInputStream() and getOutputStream(). I could use these
stream objects, as is, to send data to the program on the other end of
the socket. But these generic input and output streams are unbuffered.
This means that every time I write even the smallest piece of data to the
stream, an entire TCP/IP packet will be created and sent across the socket.
They're also byte-oriented, which means I'd have to convert all of my data
into pure bytes, send it across, and convert it back on the other end.
Keep in mind that a byte-oriented protocol is called for in some instances,
but for most of my applications, I think it's simpler to think of
sending data in a stream. That is, if I have a Java object "Car"
in my program, when I send it across a stream, I want to send a "Car",
not just a bunch of bytes.
I can enable buffering by wrapping a BufferedInputStream or
BufferedOutputStream around my simple streams. Then I can wrap the
DataInputStream and DataOutputStream classes around the buffered
streams. The "DataXxxStream" classes offer me an API
that is much broader. Instead of requiring me to convert my integers into
bytes, I can simply call outstream.writeInt() with my integer,
and DataOutputStream converts that Java int into the
bytes. Then on the other end, another Java program can use the DataInputStream
method instream.readInt(), which returns the value as an
int. Simple!
And there's something even more important here. Hidden in this elegant
API is the fact that the Java library has implemented a standard
method for reading and writing primitive data types so that they are consistent
between the two processes. Strings are written and read using the functions
writeUTF() and readUTF(). (UTF stands for Unicode
Transfer Format and is a standard, compact format for transferring
the Unicode strings that Java uses). And all numeric data is written in
a consistent byte order (called network byte order) so that Java
programs running on any type of machine can communicate according to a
universally adopted low-level protocol. Therefore, using Java on both
the client and the server will guarantee that these primitive data objects
are converted and interpreted correctly, on both ends.
The next implementation of Java, version 1.1, includes an extremely
attractive new feature for client-server implementations: Object
Serialization. This feature will seamlessly encode your Java objects
into a stream of bytes for transmission across the socket (or to a file),
and then decode and reconstruct the objects as they are received. Object
Serialization will greatly simplify Java communication patterns.
Design or Choose a Protocol
When most of us write more than the simplest of object-oriented or even
structured programs, we don't just create a whole bunch of ints,
floats, doubles, and strings, and toss them around.
We create classes or structures and collect the relevant data in encapsulated
objects. Similarly, when you pass data from one program to another, you
have to design a protocol that is usually a little more sophisticated than
something that says "pass a bunch of numbers to the server."
A higher-level protocol defines the standard by which the client and
server will communicate. It's the "rules of engagement," including
how a client introduces itself, how the server and client will make requests
and or respond to one another, and how they will terminate the transaction.
The great thing about the openness of the Internet is that there are a
number of open standards for Internet communication. Depending on your
application, you may want - or need - to adopt one of these standards.
For instance, if you were to write an email client, you would want to
get familiar with protocols such as SMTP, POP3, and IMAP4. These are TCP/IP-based
standards for talking to existing mail servers. (In fact, you could be
looking at implementing a mail server in Java, and you would need one or
more of these protocols to talk to any email client.) Some other client/server
protocol examples include JDBC and LDAP. These may also be appropriate
for your application, depending on your needs.
My point here is that you can choose an existing standard protocol -
whether it's open, or some company or industry standard - and implement
the protocol in Java.You can also design your own protocol. Now the sky's
the limit. If you are designing a new client/server capability, you can
decide what data is transferred and how.
An Example Client/Server Application
As an example, suppose you work for one of the "Big Three"
auto manufacturers, and you are directed to implement a new thin-client
strategy. Your first assignment is to develop a Java applet that would
be used by hundreds or thousands of people who have input into the development
of new vehicles. This applet will show the current parts inventory for
one of a selection of new car designs, and it will allow the user to add
parts to the inventory. It might look something like the following do-nothing
applet:
When you design the protocol for the application, you might come up
with a number of message types, including the following:
- Client-to-server: "Give me the inventory, one part at a time,
for car X"
- Server-to-client: "Here is a part for car X"
- Client-to-server: "Add, remove, or change this part for car X"
Write Methods that Support Your Protocol
Once you decide exactly what commands and data you want to transfer,
and when you want to transfer the data, you can start to develop Java methods
to read and write the data. Below are some sample methods that could support
our parts inventory protocol. To send a new part to the server, the client
applet could call writeAddPart, which writes a command requesting
the server to add a part with the given data elements:
public void writeAddPart( DataOutput outstream, Part part )
throws IOException {
outstream.writeUTF( "Add Part" ); // this is the command the server will respond to
outstream.writeUTF( part.getName() );
outstream.writeInt( part.getQuantity() );
outstream.writeUTF( part.getManufacturer() );
}
The next method, readFromServer, reads the next response
from the server (a string that could be one of possibly many different
response codes), and performs an appropriate action. In this example, only
a Part response is accepted. The function then calls readPart
to generate a Part object from the server, and adds it to
its own internal list:
public void readFromServer( DataInput instream ) throws IOException {
// this goes into an infinite loop, so you will want to run this in a Thread
while ( true ) {
String command = instream.readUTF();
if ( command.equals( "Part" ) ) {
Part part = readPart( instream );
partsList.addPart( part ); // partsList is an application object referenced by this class
}
else { // add more commands here, if any
throw new IOException( "Unexpected command: "+command );
}
}
}
The next method is readPart, which simply creates a new Part
object and reads the attributes from the stream. Note that this function
could be used on both the client - to read the part from the server, as
shown above - or on the server - to read a part that the client added.
The function should probably be put in a utility library for the parts
inventory application, and used by both client and server:
public Part readPart( DataInput instream ) throws IOException {
Part part = new Part();
part.setName( instream.readUTF() );
part.setQuantity( instream.readInt() );
part.setManufacturer( instream.readUTF() );
return part;
}
Selecting the JVM Environment for Your Server
All Java programs run in what is referred to as a Java virtual machine
(JVM). This is typically a single process that includes a byte-code interpreter
or just-in-time (JIT) compiler, loads and executes Java classes, and manages
the threads, memory allocation, and garbage collection. (Interpreters and
JITs read and execute Java byte-cod, the Java equivalent of machine code,
which is portable to any platform. JITs can actually compile some of the
byte-code to much faster native machine code as the application loads.)
Netscape Navigator includes an embedded JVM in which Java applets, downloaded
across the Internet, are loaded and run.
The latest Web servers - including Netscape Enterprise Server - now
include their own embedded JVMs. Instead of loading client applets over
the Internet, the server loads server applets from its local file system
in response to URL requests. One of the advantages to this approach is
that - in the case of Netscape Enterprise Server - server applets have
access to the Web server's environment and capabilities. They can also
be used to respond to HTTP protocol requests from Web browsers in a flexible
and dynamic way. The Netscape
server Java API adds a number of convenience classes that make HTTP
transactions simple and painless. (I'll devote much more detail to the
topic of Netscape server applets and their capabilities in Part 2 of this
article.)
When you write a Java server, one of the things you have to decide is
what your Java environment will be. If your Java server is not embedded
in a Web server or similar environment, then you can run your Java application
using one of the JavaSoft Java interpreters
or JITs, one from your system vendor, or one from a third party. These
JVMs are usually distributed as part of the Java Developer's Kit (JDK)
or an integrated development environment (IDE) like Symantec Café
or Sun's JavaWorkshop. (Enough with the acronyms!)
Loading a Stand-alone Java Application
The stand-alone interpreters typically run under the name java
(easy enough to remember!). And to run a stand-alone Java application from
a DOS or UNIX shell command line, you can type java MyApp, where
MyApp.class is accessible through your CLASSPATH. Without getting
into a too much of a Java tutorial, stand-alone Java applications start
from a "main" function (very similar to the way you'd start applications
written in C and C++). When you run the interpreter on a Java class file,
the interpreter scans that class file for a static function with
the exact signature public static void main( String args[] ),
and executes that function.
From here, I create a Server object that, when constructed, will open
a Java ServerSocket. ServerSocket is a class in the standard
java.net package, and it opens a specific TCP/IP port on the server
to which clients can connect. The main responsibility of the server is
to loop indefinitely, and call the ServerSocket accept() method,
which returns (with a new Socket object) only when a new client
has connected. In the Java model, as each client connects, you have one
of two choices:
- If your client-server protocol implements client/server dialogs of
very short duration, you may want to simply respond to the client at this
point in your loop, then drop the client (that is, close the socket) and
go back to accepting new clients.
- If you don't want to force your other potential clients to wait for
the server to respond to another client before they are accepted, or if
the client/server dialog is going to persist for an indefinite period,
then you will want to create a thread for each new client. The server object
should start the thread and then return in the loop to accept more clients.
A Useful Package of Java Client/Server Utilities
I've developed a small package of classes - dtai.net - that
provide a generic framework for almost any client/server architecture.
We have used this numerous times at DTAI as a foundation for many of our
client/server applications. I've included the source code with this article.
Feel free to use these classes in your own client/server implementations.
The classes include NetServer
and NetPort;
NetPort is subclassed into two versions, NetPortToClient
(used by the server) and NetPortToServer
(used by the client). These classes are meant to be subclasses that
you can extend to meet your specific needs. They handle most of the thread,
socket, and data streaming code, and they allow you to concentrate on the
issues that are specific to your application.
As a client/server developer, you would subclass NetServer and
NetPortToClient for your server-side program. For simple servers,
I often do this in one .java file, with two classes. To give you
an example, I've also included 45-line server program with this article,
called Sample2.java.
Sample2.java doesn't really do anything useful. It simply reads a command
string from the client and responds to the client by writing a new string
with the value I did it!. But it's very easy to make this program
more functional, simply by adding code to the readInput() function.
Some aspects of the program to pay attention to are the following:
- The server starts listening for clients as soon as the NetServer
constructor is invoked, through the call super( portnum ).
NetServer is a subclass of Thread. The NetServer constructor
not only opens the ServerSocket for the given port number, but
it also starts a thread in which to loop and wait for incoming client connections.
This way, the superclass returns, and you could, potentially, create another
server, perhaps of a different class.
- When a client connects, NetServer calls createClientPort().
This abstract method is overridden in the sample to create the appropriate
type (the programmer's subclass) of a NetPortToClient. NetPortToClient
also extends Thread, and here in createClientPort() we
also start this Thread. (Optionally, we could have coded this
for quick transient connections, but the code I've provided is a more general
approach.)
- In the NetPort object , the run() method (invoked
when starting a Thread) loops and calls readInput().
Here is where I placed the server-side of the client/server dialog. Each
time the server calls this function, the function should read the next
command, or request, from the client; it should block, that is, hang on
this line until something is available to read, if the client hasn't sent
anything yet. When the request is received, the NetPortToClient subclass
should perform the requested action, and return the appropriate response,
if any. This dialog is entirely flexible and can be altered to suit any
needs. (For example you could write a POP3 mail server, or the parts inventory
server shown above.)
- Also notice that the reads and writes refer to the NetPort object
itself. This useful shorthand is possible because NetPort implements
both the DataInput and DataOutput interfaces, and delegates
the calls to the buffered DataInput and DataOutput streams
associated with the socket to the client.
- Finally, because we use buffered streams, we need to do a flush
to ensure that the data that's been written to the client socket gets
sent immediately.
The Client Side
The code for the client side is even simpler. NetPortToServer also
subclasses NetPort, and it has the advantages of the DataInput
and DataOutput implementations as well as being able to subclass
Thread. (Again, use of the Thread and the readInput()
model is optional.) The constructor for NetPortToServer takes
the server's host identifier (hostname or Internet address) and the server's
port number. I often use the NetPortToServer class as-is, without
subclassing.
Here is a short, three-line example of how easy it is to invoke a simple
client-to-server transaction:
port = new NetPortToServer( host, portnum );
port.writeUTF( "Do something for me" );
String response = port.readUTF();
Obviously, in this example, the protocol was predetermined. The server
should be waiting for a command, in the form of a UTF String. It should
also respond with one UTF String. If anything goes wrong, the server could
send an error message back in that string or simply shut down the connection.
What I haven't shown is that you will have to wrap these lines inside of
a try/catch block to catch any I/O exceptions, such as if the
server shuts down the connection.
Starting the Server
I've found that one of the trickier parts of client/server programming
is to figure out how to start the server in the first place. During most
of my programming, I just assume that the server is running. This is easy
to do in the beginning, because I usually start the server myself. But
when I deploy my applications, the production version has to have a way
to start the server automatically. There are several ways to do this under
UNIX:
- Start the server from an rc - UNIX run command - file. These
scripts are executed at system boot time, and there is always a best place
to put server programs like these. (Typically, they'd either be embedded
in an existing rc file, like rc.local, or placed in a
separate file in a designated rc directory.)
- Start the server from inetd (the Internet daemon). This is
quite a bit different from our background-process approach, because
inetd handles the client connections and spawns a separate process
for each one. Data on the socket is routed to the process's standard input
and standard output. I don't recommend this solution, particularly because
inetd has to spawn a separate JVM for each process, and that consumes
quite a bit of resources.
- Start the server as a background process from a CGI script. This is
a good approach, and I use it in Interactive
Org Chart. Using this approach, when clients attempt to connect to
the server, if there is an exception, the server is probably not running.
I create a Java URL object to the CGI program, use the URL openConnection()
method, and read a few bytes to get the CGI program running. The CGI
program sets up its environment to run Java, and then invokes the JVM with
the server class as a UNIX background process. The server should
be invoked only once, the first time it's accessed, unless the machine
is rebooted or the server is otherwise abnormally terminated.
Starting the server under Windows NT is a little more difficult. Unlike
UNIX, Windows NT does not treat background processes as "first-class
citizens." I developed a small C program called spawn.c
(which compiles to spawn.exe) to start a program in the background. It
usually works. (Well, I'm not much of a Windows programmer, but even the
Windows experts I know seem to get road-blocked on Windows background
processes!) When it does work, it is consistent. Using this program, you
can write a DOS batch file as a CGI program and spawn the server as a background
process.
Another alternative for Windows NT, similar to the rc file
approach, is to start the Java server as a Windows NT Service. Microsoft
has a utility program that comes with its Java Software Developer's Kit
(SDK) that will register a class as an NT Service. You have to create a
small "wrapper" class that subclasses a Microsoft class file
(which is included with the SDK). It is relatively simple to use, but I
still prefer the straightforward and debuggable UNIX rc scripts.
(Such is life.)
Conclusion
Java is an extremely powerful language for client/server programming.
And although Java is already seen as an applicable client-side software
tool, don't overlook Java for other applications, including server-side
development. Java is maturing rapidly, and there are even promises that
Java will perform as fast as any other modern programming language by year-end.
In my opinion, Java is the best choice for most programming tasks, even
today. In Part 2 of this article, I'll discuss Netscape's Server Applets,
and new features in Enterprise Server 3.0, including server-side LiveConnect,
that allow Java programs more control and interoperability with other parts
of the Netscape Web server.
Technical reviewer for this article was Junhe Liu of Netscape Communications'
developer support engineering team.
View Source wants your feedback!
Write to us and let us
know what you think of this article.
Rich Kadel is
a senior software engineer and lead engineer in charge of Java development
at DTAI Incorporated, a progressive
technology company based in San Diego, CA. DTAI develops advanced
software products and provides custom computer solutions to a number of
market areas. Rich currently leads the development of DTAI's Interactive
Org Chart, a 100 percent Java client/server Intranet application
first developed for Netscape's AppFoundry program. During the production
of Interactive Org Chart 1.0, he developed the Gadget
Windowing Toolkit (GWT), a powerful, portable, robust (and now free!)
replacement for Java's AWT. He also participates in the development of
DTAI's JustDBC suite
of JDBC (Java Database Connectivity) drivers, proxies, and servers. He
was the primary developer of PAL++, a large C++ software library with almost
300 C++ classes for building graphic- and data-oriented applications.
(5.97)
- Related Reading:
Any sample code included above is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use