MindView Inc.
[ Viewing Hints ] [ Revision History ] [ Report an Error ]
[ 1st Edition ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 2nd edition, Revision 9

©2000 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

15: Distributed Computing

Historically, programming across multiple machines has been error-prone, difficult, and complex.

The programmer had to know many details about the network and sometimes even the hardware. You usually needed to understand the various “layers” of the networking protocol, and there were a lot of different functions in each different networking library concerned with connecting, packing, and unpacking blocks of information; shipping those blocks back and forth; and handshaking. It was a daunting task.

However, the concept of networking is not so difficult. You want to get some information from that machine over there and move it to this machine here, or vice versa. It’s quite similar to reading and writing files, except that the file exists on a remote machine and the remote machine can decide exactly what it wants to do about the information you’re requesting or sending.

One of Java’s great strengths is painless networking. As much as possible, the underlying details of networking have been abstracted away and taken care of within the JVM and local machine installation of Java. The programming model you use is that of a file; in fact, you actually wrap the network connection (a “socket”) with stream objects, so you end up using the same method calls as you do with all other streams. In addition, Java’s built-in multithreading is exceptionally handy when dealing with another networking issue: handling multiple connections at once.

This chapter introduces Java’s networking support using easy-to-understand examples.

Network programming

Identifying a machine

Of course, in order to tell one machine from another and to make sure that you are connected with the machine you want, there must be some way of uniquely identifying machines on a network. Early networks were satisfied to provide unique names for machines within the local network. However, Java works within the Internet, which requires a way to uniquely identify a machine from all the others in the world. This is accomplished with the IP (Internet Protocol) address that can exist in two forms:

  1. The familiar DNS (Domain Name System) form. My domain name is bruceeckel.com, so suppose I have a computer called Opus in my domain. Its domain name would be Opus.bruceeckel.com. This is exactly the kind of name that you use when you send email to people, and is often incorporated into a World-Wide-Web address.
  2. Alternatively, you can use the “dotted quad” form, which is four numbers separated by dots, such as 123.255.28.120.

In both cases, the IP address is represented internally as a 32-bit number[68] (so each of the quad numbers cannot exceed 255), and you can get a special Java object to represent this number from either of the forms above by using the static InetAddress.getByName( ) method that’s in java.net. The result is an object of type InetAddress that you can use to build a “socket” as you will see later.

As a simple example of using InetAddress.getByName( ), consider what happens if you have a dial-up Internet service provider (ISP). Each time you dial up, you are assigned a temporary IP address. But while you’re connected, your IP address has the same validity as any other IP address on the Internet. If someone connects to your machine using your IP address then they can connect to a Web server or FTP server that you have running on your machine. Of course, they need to know your IP address, and since it’s assigned each time you dial up, how can you find out what it is?

The following program uses InetAddress.getByName( ) to produce your IP address. To use it, you must know the name of your computer. It has been tested only on Windows 95, but there you can go to “Settings,” “Control Panel,” “Network,” and then select the “Identification” tab. “Computer name” is the name to put on the command line.

//: c15:WhoAmI.java
// Finds out your network address when
// you're connected to the Internet.
import java.net.*;

public class WhoAmI {
  public static void main(String[] args) 
      throws Exception {
    if(args.length != 1) {
      System.err.println(
        "Usage: WhoAmI MachineName");
      System.exit(1);
    }
    InetAddress a = 
      InetAddress.getByName(args[0]);
    System.out.println(a);
  }
} ///:~

In this case, the machine is called “peppy.” So, once I’ve connected to my ISP I run the program:

java WhoAmI peppy

I get back a message like this (of course, the address is different each time):

peppy/199.190.87.75

If I tell my friend this address, he can log onto my personal Web server by going to the URL http://199.190.87.75 (only as long as I continue to stay connected during that session). This can sometimes be a handy way to distribute information to someone else or to test out a Web site configuration before posting it to a “real” server.

Servers and clients

The whole point of a network is to allow two machines to connect and talk to each other. Once the two machines have found each other they can have a nice, two-way conversation. But how do they find each other? It’s like getting lost in an amusement park: one machine has to stay in one place and listen while the other machine says, “Hey, where are you?”

The machine that “stays in one place” is called the server, and the one that seeks is called the client. This distinction is important only while the client is trying to connect to the server. Once they’ve connected, it becomes a two-way communication process and it doesn’t matter anymore that one happened to take the role of server and the other happened to take the role of the client.

So the job of the server is to listen for a connection, and that’s performed by the special server object that you create. The job of the client is to try to make a connection to a server, and this is performed by the special client object you create. Once the connection is made, you’ll see that at both server and client ends, the connection is just magically turned into an IO stream object, and from then on you can treat the connection as if you were reading from and writing to a file. Thus, after the connection is made you will just use the familiar IO commands from Chapter 11. This is one of the nice features of Java networking.

Testing programs without a network

For many reasons, you might not have a client machine, a server machine, and a network available to test your programs. You might be performing exercises in a classroom situation, or you could be writing programs that aren’t yet stable enough to put onto the network. The creators of the Internet Protocol were aware of this issue, and they created a special address called localhost to be the “local loopback” IP address for testing without a network. The generic way to produce this address in Java is:

InetAddress addr = InetAddress.getByName(null);

If you hand getByName( ) a null, it defaults to using the localhost. The InetAddress is what you use to refer to the particular machine, and you must produce this before you can go any further. You can’t manipulate the contents of an InetAddress (but you can print them out, as you’ll see in the next example). The only way you can create an InetAddress is through one of that class’s static member methods getByName( ) (which is what you’ll usually use), getAllByName( ), or getLocalHost( ).

You can also produce the local loopback address by handing it the string localhost:

InetAddress.getByName("localhost");

or by using its dotted quad form to name the reserved IP number for the loopback:

InetAddress.getByName("127.0.0.1");

All three forms produce the same result.

Port: a unique place
within the machine

An IP address isn’t enough to identify a unique server, since many servers can exist on one machine. Each IP machine also contains ports, and when you’re setting up a client or a server you must choose a port where both client and server agree to connect; if you’re meeting someone, the IP address is the neighborhood and the port is the bar.

The port is not a physical location in a machine, but a software abstraction (mainly for bookkeeping purposes). The client program knows how to connect to the machine via its IP address, but how does it connect to a desired service (potentially one of many on that machine)? That’s where the port numbers come in as second level of addressing. The idea is that if you ask for a particular port, you’re requesting the service that’s associated with the port number. The time of day is a simple example of a service. Typically, each service is associated with a unique port number on a given server machine. It’s up to the client to know ahead of time which port number the desired service is running on.

The system services reserve the use of ports 1 through 1024, so you shouldn’t use those or any other port that you know to be in use. The first choice for examples in this book will be port 8080 (in memory of the venerable old 8-bit Intel 8080 chip in my first computer, a CP/M machine).

Sockets

The socket is the software abstraction used to represent the “terminals” of a connection between two machines. For a given connection, there’s a socket on each machine, and you can imagine a hypothetical “cable” running between the two machines with each end of the “cable” plugged into a socket. Of course, the physical hardware and cabling between machines is completely unknown. The whole point of the abstraction is that we don’t have to know more than is necessary.

In Java, you create a socket to make the connection to the other machine, then you get an InputStream and OutputStream (or, with the appropriate converters, Reader and Writer) from the socket in order to be able to treat the connection as an IO stream object. There are two stream-based socket classes: a ServerSocket that a server uses to “listen” for incoming connections and a Socket that a client uses in order to initiate a connection. Once a client makes a socket connection, the ServerSocket returns (via the accept( ) method) a corresponding server side Socket through which direct communications will take place. From then on, you have a true Socket to Socket connection and you treat both ends the same way because they are the same. At this point, you use the methods getInputStream( ) and getOutputStream( ) to produce the corresponding InputStream and OutputStream objects from each Socket. These must be wrapped inside buffers and formatting classes just like any other stream object described in Chapter 11.

The use of the term ServerSocket would seem to be another example of a confusing name scheme in the Java libraries. You might think ServerSocket would be better named “ServerConnector” or something without the word “Socket” in it. You might also think that ServerSocket and Socket should both be inherited from some common base class. Indeed, the two classes do have several methods in common but not enough to give them a common base class. Instead, ServerSocket’s job is to wait until some other machine connects to it, then to return an actual Socket. This is why ServerSocket seems to be a bit misnamed, since its job isn’t really to be a socket but instead to make a Socket object when someone else connects to it.

However, the ServerSocket does create a physical “server” or listening socket on the host machine. This socket listens for incoming connections and then returns an “established” socket (with the local and remote endpoints defined) via the accept( ) method. The confusing part is that both of these sockets (listening and established) are associated with the same server socket. The listening socket can accept only new connection requests and not data packets. So while ServerSocket doesn’t make much sense programmatically, it does “physically.”

When you create a ServerSocket, you give it only a port number. You don’t have to give it an IP address because it’s already on the machine it represents. When you create a Socket, however, you must give both the IP address and the port number where you’re trying to connect. (On the other hand, the Socket that comes back from ServerSocket.accept( ) already contains all this information.)

A simple server and client

This example makes the simplest use of servers and clients using sockets. All the server does is wait for a connection, then uses the Socket produced by that connection to create an InputStream and OutputStream. After that, everything it reads from the InputStream it echoes to the OutputStream until it receives the line END, at which time it closes the connection.

The client makes the connection to the server, then creates an OutputStream. Lines of text are sent through the OutputStream. The client also creates an InputStream to hear what the server is saying (which, in this case, is just the words echoed back).

Both the server and client use the same port number and the client uses the local loopback address to connect to the server on the same machine so you don’t have to test it over a network. (For some configurations, you might need to be connected to a network for the programs to work, even if you aren’t communicating over that network.)

Here is the server:

//: c15:JabberServer.java
// Very simple server that just
// echoes whatever the client sends.
import java.io.*;
import java.net.*;

public class JabberServer {  
  // Choose a port outside of the range 1-1024:
  public static final int PORT = 8080;
  public static void main(String[] args) 
      throws IOException {
    ServerSocket s = new ServerSocket(PORT);
    System.out.println("Started: " + s);
    try {
      // Blocks until a connection occurs:
      Socket socket = s.accept();
      try {
        System.out.println(
          "Connection accepted: "+ socket);
        BufferedReader in = 
          new BufferedReader(
            new InputStreamReader(
              socket.getInputStream()));
        // Output is automatically flushed
        // by PrintWriter:
        PrintWriter out = 
          new PrintWriter(
            new BufferedWriter(
              new OutputStreamWriter(
                socket.getOutputStream())),true);
        while (true) {  
          String str = in.readLine();
          if (str.equals("END")) break;
          System.out.println("Echoing: " + str);
          out.println(str);
        }
      // Always close the two sockets...
      } finally {
        System.out.println("closing...");
        socket.close();
      }
    } finally {
      s.close();
    }
  } 
} ///:~

You can see that the ServerSocket just needs a port number, not an IP address (since it’s running on this machine!). When you call accept( ), the method blocks until some client tries to connect to it. That is, it’s there waiting for a connection but other processes can run (see Chapter 14). When a connection is made, accept( ) returns with a Socket object representing that connection.

The responsibility for cleaning up the sockets is crafted carefully here. If the ServerSocket constructor fails, the program just quits (notice we must assume that the constructor for ServerSocket doesn’t leave any open network sockets lying around if it fails). For this case, main( ) throws IOException so a try block is not necessary. If the ServerSocket constructor is successful then all other method calls must be guarded in a try-finally block to ensure that, no matter how the block is left, the ServerSocket is properly closed.

The same logic is used for the Socket returned by accept( ). If accept( ) fails, then we must assume that the Socket doesn’t exist or hold any resources, so it doesn’t need to be cleaned up. If it’s successful, however, the following statements must be in a try-finally block so that if they fail the Socket will still be cleaned up. Care is required here because sockets use important non-memory resources, so you must be diligent in order to clean them up (since there is no destructor in Java to do it for you).

Both the ServerSocket and the Socket produced by accept( ) are printed to System.out. This means that their toString( ) methods are automatically called. These produce:

ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]
Socket[addr=127.0.0.1,PORT=1077,localport=8080]

Shortly, you’ll see how these fit together with what the client is doing.

The next part of the program looks just like opening files for reading and writing except that the InputStream and OutputStream are created from the Socket object. Both the InputStream and OutputStream objects are converted to Java 1.1 Reader and Writer objects using the “converter” classes InputStreamReader and OutputStreamWriter, respectively. You could also have used the Java 1.0 InputStream and OutputStream classes directly, but with output there’s a distinct advantage to using the Writer approach. This appears with PrintWriter, which has an overloaded constructor that takes a second argument, a boolean flag that indicates whether to automatically flush the output at the end of each println( ) (but not print( )) statement. Every time you write to out, its buffer must be flushed so the information goes out over the network. Flushing is important for this particular example because the client and server each wait for a line from the other party before proceeding. If flushing doesn’t occur, the information will not be put onto the network until the buffer is full, which causes lots of problems in this example.

When writing network programs you need to be careful about using automatic flushing. Every time you flush the buffer a packet must be created and sent. In this case, that’s exactly what we want, since if the packet containing the line isn’t sent then the handshaking back and forth between server and client will stop. Put another way, the end of a line is the end of a message. But in many cases messages aren’t delimited by lines so it’s much more efficient to not use auto flushing and instead let the built-in buffering decide when to build and send a packet. This way, larger packets can be sent and the process will be faster.

Note that, like virtually all streams you open, these are buffered. There’s an exercise at the end of the chapter to show you what happens if you don’t buffer the streams (things get slow).

The infinite while loop reads lines from the BufferedReader in and writes information to System.out and to the PrintWriter out. Note that these could be any streams, they just happen to be connected to the network.

When the client sends the line consisting of “END” the program breaks out of the loop and closes the Socket.

Here’s the client:

//: c15:JabberClient.java
// Very simple client that just sends
// lines to the server and reads lines
// that the server sends.
import java.net.*;
import java.io.*;

public class JabberClient {
  public static void main(String[] args) 
      throws IOException {
    // Passing null to getByName() produces the
    // special "Local Loopback" IP address, for
    // testing on one machine w/o a network:
    InetAddress addr = 
      InetAddress.getByName(null);
    // Alternatively, you can use 
    // the address or name:
    // InetAddress addr = 
    //    InetAddress.getByName("127.0.0.1");
    // InetAddress addr = 
    //    InetAddress.getByName("localhost");
    System.out.println("addr = " + addr);
    Socket socket = 
      new Socket(addr, JabberServer.PORT);
    // Guard everything in a try-finally to make
    // sure that the socket is closed:
    try {
      System.out.println("socket = " + socket);
      BufferedReader in =
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Output is automatically flushed
      // by PrintWriter:
      PrintWriter out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())),true);
      for(int i = 0; i < 10; i ++) {
        out.println("howdy " + i);
        String str = in.readLine();
        System.out.println(str);
      }
      out.println("END");
    } finally {
      System.out.println("closing...");
      socket.close();
    }
  }
} ///:~

In main( ) you can see all three ways to produce the InetAddress of the local loopback IP address: using null, localhost, or the explicit reserved address 127.0.0.1. Of course, if you want to connect to a machine across a network you substitute that machine’s IP address. When the InetAddress addr is printed (via the automatic call to its toString( ) method) the result is:

localhost/127.0.0.1

By handing getByName( ) a null, it defaulted to finding the localhost, and that produced the special address 127.0.0.1.

Note that the Socket called socket is created with both the InetAddress and the port number. To understand what it means when you print one of these Socket objects, remember that an Internet connection is determined uniquely by these four pieces of data: clientHost, clientPortNumber, serverHost, and serverPortNumber. When the server comes up, it takes up its assigned port (8080) on the localhost (127.0.0.1). When the client comes up, it is allocated to the next available port on its machine, 1077 in this case, which also happens to be on the same machine (127.0.0.1) as the server. Now, in order for data to move between the client and server, each side has to know where to send it. Therefore, during the process of connecting to the “known” server, the client sends a “return address” so the server knows where to send its data. This is what you see in the example output for the server side:

Socket[addr=127.0.0.1,port=1077,localport=8080]

This means that the server just accepted a connection from 127.0.0.1 on port 1077 while listening on its local port (8080). On the client side:

Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]

which means that the client made a connection to 127.0.0.1 on port 8080 using the local port 1077.

You’ll notice that every time you start up the client anew, the local port number is incremented. It starts at 1025 (one past the reserved block of ports) and keeps going up until you reboot the machine, at which point it starts at 1025 again. (On UNIX machines, once the upper limit of the socket range is reached, the numbers will wrap around to the lowest available number again.)

Once the Socket object has been created, the process of turning it into a BufferedReader and PrintWriter is the same as in the server (again, in both cases you start with a Socket). Here, the client initiates the conversation by sending the string “howdy” followed by a number. Note that the buffer must again be flushed (which happens automatically via the second argument to the PrintWriter constructor). If the buffer isn’t flushed, the whole conversation will hang because the initial “howdy” will never get sent (the buffer isn’t full enough to cause the send to happen automatically). Each line that is sent back from the server is written to System.out to verify that everything is working correctly. To terminate the conversation, the agreed-upon “END” is sent. If the client simply hangs up, then the server throws an exception.

You can see that the same care is taken here to ensure that the network resources represented by the Socket are properly cleaned up, using a try-finally block.

Sockets produce a “dedicated” connection that persists until it is explicitly disconnected. (The dedicated connection can still be disconnected un-explicitly if one side, or an intermediary link, of the connection crashes.) This means the two parties are locked in communication and the connection is constantly open. This seems like a logical approach to networking, but it puts an extra load on the network. Later in the chapter you’ll see a different approach to networking, in which the connections are only temporary.

Serving multiple clients

The JabberServer works, but it can handle only one client at a time. In a typical server, you’ll want to be able to deal with many clients at once. The answer is multithreading, and in languages that don’t directly support multithreading this means all sorts of complications. In Chapter 14 you saw that multithreading in Java is about as simple as possible, considering that multithreading is a rather complex topic. Because threading in Java is reasonably straightforward, making a server that handles multiple clients is relatively easy.

The basic scheme is to make a single ServerSocket in the server and call accept( ) to wait for a new connection. When accept( ) returns, you take the resulting Socket and use it to create a new thread whose job is to serve that particular client. Then you call accept( ) again to wait for a new client.

In the following server code, you can see that it looks similar to the JabberServer.java example except that all of the operations to serve a particular client have been moved inside a separate thread class:

//: c15:MultiJabberServer.java
// A server that uses multithreading 
// to handle any number of clients.
import java.io.*;
import java.net.*;

class ServeOneJabber extends Thread {
  private Socket socket;
  private BufferedReader in;
  private PrintWriter out;
  public ServeOneJabber(Socket s) 
      throws IOException {
    socket = s;
    in = 
      new BufferedReader(
        new InputStreamReader(
          socket.getInputStream()));
    // Enable auto-flush:
    out = 
      new PrintWriter(
        new BufferedWriter(
          new OutputStreamWriter(
            socket.getOutputStream())), true);
    // If any of the above calls throw an 
    // exception, the caller is responsible for
    // closing the socket. Otherwise the thread
    // will close it.
    start(); // Calls run()
  }
  public void run() {
    try {
      while (true) {  
        String str = in.readLine();
        if (str.equals("END")) break;
        System.out.println("Echoing: " + str);
        out.println(str);
      }
      System.out.println("closing...");
    } catch (IOException e) {
    } finally {
      try {
        socket.close();
      } catch(IOException e) {}
    }
  }
}

public class MultiJabberServer {  
  static final int PORT = 8080;
  public static void main(String[] args)
      throws IOException {
    ServerSocket s = new ServerSocket(PORT);
    System.out.println("Server Started");
    try {
      while(true) {
        // Blocks until a connection occurs:
        Socket socket = s.accept();
        try {
          new ServeOneJabber(socket);
        } catch(IOException e) {
          // If it fails, close the socket,
          // otherwise the thread will close it:
          socket.close();
        }
      }
    } finally {
      s.close();
    }
  } 
} ///:~

The ServeOneJabber thread takes the Socket object that’s produced by accept( ) in main( ) every time a new client makes a connection. Then, as before, it creates a BufferedReader and auto-flushed PrintWriter object using the Socket. Finally, it calls the special Thread method start( ), which performs thread initialization and then calls run( ). This performs the same kind of action as in the previous example: reading something from the socket and then echoing it back until it reads the special “END” signal.

The responsibility for cleaning up the socket must again be carefully designed. In this case, the socket is created outside of the ServeOneJabber so the responsibility can be shared. If the ServeOneJabber constructor fails, it will just throw the exception to the caller, who will then clean up the thread. But if the constructor succeeds, then the ServeOneJabber object takes over responsibility for cleaning up the thread, in its run( ).

Notice the simplicity of the MultiJabberServer. As before, a ServerSocket is created and accept( ) is called to allow a new connection. But this time, the return value of accept( ) (a Socket) is passed to the constructor for ServeOneJabber, which creates a new thread to handle that connection. When the connection is terminated, the thread simply goes away.

If the creation of the ServerSocket fails, the exception is again thrown through main( ). But if it succeeds, the outer try-finally guarantees its cleanup. The inner try-catch guards only against the failure of the ServeOneJabber constructor; if the constructor succeeds, then the ServeOneJabber thread will close the associated socket.

To test that the server really does handle multiple clients, the following program creates many clients (using threads) that connect to the same server. Each thread has a limited lifetime, and when it goes away, that leaves space for the creation of a new thread. The maximum number of threads allowed is determined by the final int maxthreads. You’ll notice that this value is rather critical, since if you make it too high the threads seem to run out of resources and the program mysteriously fails.

//: c15:MultiJabberClient.java
// Client that tests the MultiJabberServer
// by starting up multiple clients.
import java.net.*;
import java.io.*;

class JabberClientThread extends Thread {
  private Socket socket;
  private BufferedReader in;
  private PrintWriter out;
  private static int counter = 0;
  private int id = counter++;
  private static int threadcount = 0;
  public static int threadCount() { 
    return threadcount; 
  }
  public JabberClientThread(InetAddress addr) {
    System.out.println("Making client " + id);
    threadcount++;
    try {
      socket = 
        new Socket(addr, MultiJabberServer.PORT);
    } catch(IOException e) {
      // If the creation of the socket fails, 
      // nothing needs to be cleaned up.
    }
    try {    
      in = 
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Enable auto-flush:
      out = 
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())), true);
      start();
    } catch(IOException e) {
      // The socket should be closed on any 
      // failures other than the socket 
      // constructor:
      try {
        socket.close();
      } catch(IOException e2) {}
    }
    // Otherwise the socket will be closed by
    // the run() method of the thread.
  }
  public void run() {
    try {
      for(int i = 0; i < 25; i++) {
        out.println("Client " + id + ": " + i);
        String str = in.readLine();
        System.out.println(str);
      }
      out.println("END");
    } catch(IOException e) {
    } finally {
      // Always close it:
      try {
        socket.close();
      } catch(IOException e) {}
      threadcount--; // Ending this thread
    }
  }
}

public class MultiJabberClient {
  static final int MAX_THREADS = 40;
  public static void main(String[] args) 
      throws IOException, InterruptedException {
    InetAddress addr = 
      InetAddress.getByName(null);
    while(true) {
      if(JabberClientThread.threadCount() 
         < MAX_THREADS)
        new JabberClientThread(addr);
      Thread.currentThread().sleep(100);
    }
  }
} ///:~

The JabberClientThread constructor takes an InetAddress and uses it to open a Socket. You’re probably starting to see the pattern: the Socket is always used to create some kind of Reader and/or Writer (or InputStream and/or OutputStream) object, which is the only way that the Socket can be used. (You can, of course, write a class or two to automate this process instead of doing all the typing if it becomes painful.) Again, start( ) performs thread initialization and calls run( ). Here, messages are sent to the server and information from the server is echoed to the screen. However, the thread has a limited lifetime and eventually completes. Note that the socket is cleaned up if the constructor fails after the socket is created but before the constructor completes. Otherwise the responsibility for calling close( ) for the socket is relegated to the run( ) method.

The threadcount keeps track of how many JabberClientThread objects currently exist. It is incremented as part of the constructor and decremented as run( ) exits (which means the thread is terminating). In MultiJabberClient.main( ), you can see that the number of threads is tested, and if there are too many, no more are created. Then the method sleeps. This way, some threads will eventually terminate and more can be created. You can experiment with MAX_THREADS to see where your particular system begins to have trouble with too many connections.

Datagrams

The examples you’ve seen so far use the Transmission Control Protocol (TCP, also known as stream-based sockets), which is designed for ultimate reliability and guarantees that the data will get there. It allows retransmission of lost data, it provides multiple paths through different routers in case one goes down, and bytes are delivered in the order they are sent. All this control and reliability comes at a cost: TCP has a high overhead.

There’s a second protocol, called User Datagram Protocol (UDP), which doesn’t guarantee that the packets will be delivered and doesn’t guarantee that they will arrive in the order they were sent. It’s called an “unreliable protocol” (TCP is a “reliable protocol”), which sounds bad, but because it’s much faster it can be useful. There are some applications, such as an audio signal, in which it isn’t so critical if a few packets are dropped here or there but speed is vital. Or consider a time-of-day server, where it really doesn’t matter if one of the messages is lost. Also, some applications might be able to fire off a UDP message to a server and can then assume, if there is no response in a reasonable period of time, that the message was lost.

Typically, you’ll do most of your direct network programming with TCP, and only occasionally will you use UDP. There’s a more complete treatment of UDP in the first edition of this book (available on the CD ROM bound into this book, or as a free download from www.BruceEckel.com).

Enterprise programming concepts

Enterprise computing is about collecting and distributing information.

You do this by creating common repositories (single points of access) to that information, and allowing people to get at that information in multiple ways. So enterprise computing is creating and manipulating those common repositories, and providing ways for users to view and manipulate the information in those repositories.

In this chapter you’ll see that there are a number of different ways to achieve this goal. The varied mechanics needed for the collection and distribution of information has to do with the variety of clients we must deal with (different application protocols) and the variety of repositories holding our enterprise data (relational databases, hierarchical databases, files, newsgroups and Email).

Since we can loosely define the any enterprise as a community of individuals working together let's leverage this definition and create a "Community Information System" (CIS) that will allow members of the community to collect and distribute information about what is going on within their group. This group could be 20 people or 20,000 but some of the services that may be needed would include the following:

So lets start by looking at an API set that provides a common interface to all those relational databases.

Java Database Connectivity (JDBC)

It has been estimated that half of all software development involves client/server operations. A great promise of Java has been the ability to build platform-independent client/server database applications. In Java 1.1 this has come to fruition with Java DataBase Connectivity (JDBC).

One of the major problems with databases has been the feature wars between the database companies. There is a “standard” database language, Structured Query Language (SQL-92), but usually you must know which database vendor you’re working with despite the standard. JDBC is designed to be platform-independent, so you don’t need to worry about the database you’re using while you’re programming. However, it’s still possible to make vendor-specific calls from JDBC so you aren’t restricted from doing what you must.

One place where programmers may need to use SQL type names is in the SQL TABLE CREATE statement when they are creating a new database table and defining the SQL type for each column. Unfortunately there are significant variations between SQL types supported by different database products. Different databases that support SQL types with the same semantics and structure may give those types different names. Most major databases support an SQL data type for large binary values, in Oracle this type is called a LONG RAW, Sybase calls it IMAGE, Informix calls it BYTE, and DB2 LONG VARCHAR FOR BIT DATA. Therefore, if database portability is a goal you should try to use only generic SQL type identifiers.

Portability is an issue when writing for a book where readers may be testing the examples with all kinds of unknown data stores. We have tried to write these examples to be as portable as possible. You should also notice that all the database specific code has been pulled out to a single class file to centralize any changes that may need to be made to get the examples operational in your environment.

JDBC, like many of the APIs in Java, is designed for simplicity. The method calls you make correspond to the logical operations you’d think of doing when gathering data from a database: connect to the database, create a statement and execute the query, and look at the result set.

To allow this platform independence, JDBC provides a driver manager that dynamically maintains all the driver objects that your database queries will need. So if you have three different kinds of vendor databases to connect to, you’ll need three different driver objects. The driver objects register themselves with the driver manager at the time of loading, and you can force the loading using Class.forName( ).

To open a database, you must create a “database URL” that specifies:

  1. That you’re using JDBC with “jdbc”
  2. The “subprotocol”: the name of the driver or the name of a database connectivity mechanism. Since the design of JDBC was inspired by ODBC, the first subprotocol available is the “jdbc-odbc bridge,” specified by “odbc”
  3. The database identifier. This varies with the database driver used, but it generally provides a logical name that is mapped by the database administration software to a physical directory where the database tables are located. For your database identifier to have any meaning, you must register the name using your database administration software. (The process of registration varies from platform to platform.)

All this information is combined into one string, the “database URL.” For example, to connect through the ODBC subprotocol to a database identified as “people,” the database URL could be:

String dbUrl = "jdbc:odbc:people";

If you’re connecting across a network, the database URL will also contain the information identifying the remote machine.

When you’re ready to connect to the database, you call the static method DriverManager.getConnection( ), passing it the database URL, the user name, and a password to get into the database. You get back a Connection object that you can then use to query and manipulate the database.

The following example opens a database of contact information and looks for a person’s last name as given on the command line. It selects only the names of people that have email addresses, then prints out all the ones that match the given last name:

//: c15:jdbc:Lookup.java
// Looks up email addresses in a 
// local database using JDBC.
import java.sql.*;

public class Lookup {
  public static void main(String[] args) {
    String dbUrl = "jdbc:odbc:people";
    String user = "";
    String password = "";
    try {
      // Load the driver (registers itself)
      Class.forName(
        "sun.jdbc.odbc.JdbcOdbcDriver");
      Connection c = DriverManager.getConnection(
        dbUrl, user, password);
      Statement s = c.createStatement();
      // SQL code:
      ResultSet r = 
        s.executeQuery(
          "SELECT FIRST, LAST, EMAIL " +
          "FROM people.csv people " +
          "WHERE " +
          "(LAST='" + args[0] + "') " +
          " AND (EMAIL Is Not Null) " +
          "ORDER BY FIRST");
      while(r.next()) {
        // Capitalization doesn't matter:
        System.out.println(
          r.getString("Last") + ", " 
          + r.getString("fIRST")
          + ": " + r.getString("EMAIL") );
      }
      s.close(); // Also closes ResultSet
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

You can see the creation of the database URL as previously described. In this example, there is no password protection on the database so the user name and password are empty strings.

Once the connection is made with DriverManager.getConnection( ), you can use the resulting Connection object to create a Statement object using the createStatement( ) method. With the resulting Statement, you can call executeQuery( ), passing in a string containing an SQL-92 standard SQL statement. (You’ll see shortly how you can generate this statement automatically, so you don’t have to know much about SQL.)

The executeQuery( ) method returns a ResultSet object, which is quite a bit like an iterator: the next( ) method moves the iterator to the next record in the statement, or returns false if the end of the result set has been reached. You’ll always get a ResultSet object back from executeQuery( ) even if a query results in an empty set (that is, an exception is not thrown). Note that you must call next( ) once before trying to read any record data. If the result set is empty, this first call to next( ) will return false. For each record in the result set, you can select the fields using (among other approaches) the field name as a string. Also note that the capitalization of the field name is ignored—it doesn’t matter with an SQL database. You determine the type you’ll get back by calling getInt( ), getString( ), getFloat( ), etc. At this point, you’ve got your database data in Java native format and can do whatever you want with it using ordinary Java code.

Getting the example to work

With JDBC, understanding the code is relatively simple. The confusing part is making it work on your particular system. The reason this is confusing is that it requires you to figure out how to get your JDBC driver to load properly, and how to set up a database using your database administration software.

Of course, this process can vary radically from machine to machine, but the process I used to make it work under 32-bit Windows might give you clues to help you attack your own situation.

Step 1: Find the JDBC Driver

The program above contains the statement:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

This implies a directory structure, which is deceiving. With this particular installation of JDK 1.1, there was no file called JdbcOdbcDriver.class, so if you looked at this example and went searching for it you’d be frustrated. Other published examples use a pseudo name, such as “myDriver.ClassName,” which is less than helpful. In fact, the load statement above for the jdbc-odbc driver (the only one that actually comes with JDK 1.1) appears in only a few places in the online documentation (in particular, a page labeled “JDBC-ODBC Bridge Driver”). If the load statement above doesn’t work, then the name might have been changed as part of a Java version change, so you should hunt through the documentation again.

If the load statement is wrong, you’ll get an exception at this point. To test whether your driver load statement is working correctly, comment out the code after the statement and up to the catch clause; if the program throws no exceptions it means that the driver is loading properly.

Step 2: Configure the database

Again, this is specific to 32-bit Windows; you might need to do some research to figure it out for your own platform.

First, open the control panel. You might find two icons that say “ODBC.” You must use the one that says “32bit ODBC,” since the other one is for backward compatibility with 16-bit ODBC software and will produce no results for JDBC. When you open the “32bit ODBC” icon, you’ll see a tabbed dialog with a number of tabs, including “User DSN,” “System DSN,” “File DSN,” etc., in which “DSN” means “Data Source Name.” It turns out that for the JDBC-ODBC bridge, the only place where it’s important to set up your database is “System DSN,” but you’ll also want to test your configuration and create queries, and for that you’ll also need to set up your database in “File DSN.” This will allow the Microsoft Query tool (that comes with Microsoft Office) to find the database. Note that other query tools are also available from other vendors.

The most interesting database is one that you’re already using. Standard ODBC supports a number of different file formats including such venerable workhorses as DBase. However, it also includes the simple “comma-separated ASCII” format, which virtually every data tool has the ability to write. In my case, I just took my “people” database that I’ve been maintaining for years using various contact-management tools and exported it as a comma-separated ASCII file (these typically have an extension of .csv). In the “File DSN” section I chose “Add,” chose the text driver to handle my comma-separated ASCII file, and then un-checked “use current directory” to allow me to specify the directory where I exported the data file.

You’ll notice when you do this that you don’t actually specify a file, only a directory. That’s because a database is typically represented as a collection of files under a single directory (although it could be represented in other forms as well). Each file usually contains a single table, and the SQL statements can produce results that are culled from multiple tables in the database (this is called a join). A database that contains only a single table (like this one) is usually called a flat-file database. Most problems that go beyond the simple storage and retrieval of data generally require multiple tables that must be related by joins to produce the desired results, and these are called relational databases.

Step 3: Test the configuration

To test the configuration you’ll need a way to discover whether the database is visible from a program that queries it. Of course, you can simply run the JDBC program example above up to and including the statement:

Connection c = DriverManager.getConnection(
  dbUrl, user, password);

If an exception is thrown, your configuration was incorrect.

However, it’s useful to get a query-generation tool involved at this point. I used Microsoft Query that came with Microsoft Office, but you might prefer something else. The query tool must know where the database is, and Microsoft Query required that I go to the ODBC Administrator’s “File DSN” tab and add a new entry there, again specifying the text driver and the directory where my database lives. You can name the entry anything you want, but it’s helpful to use the same name you used in “System DSN.”

Once you’ve done this, you will see that your database is available when you create a new query using your query tool.

Step 4: Generate your SQL query

The query that I created using Microsoft Query not only showed me that my database was there and in good order, but it also automatically created the SQL code that I needed to insert into my Java program. I wanted a query that would search for records that had the last name that was typed on the command line when starting the Java program. So as a starting point, I searched for a specific last name, ‘Eckel’. I also wanted to display only those names that had email addresses associated with them. The steps I took to create this query were:

  1. Start a new query and use the Query Wizard. Select the “people” database. (This is the equivalent of opening the database connection using the appropriate database URL.)
  2. Select the “people” table within the database. From within the table, choose the columns FIRST, LAST, and EMAIL.
  3. Under “Filter Data,” choose LAST and select “equals” with an argument of Eckel. Click the “And” radio button.
  4. Choose EMAIL and select “Is not Null.”
  5. Under “Sort By,” choose FIRST.

The result of this query will show you whether you’re getting what you want.

Now you can press the SQL button and without any research on your part, up will pop the correct SQL code, ready for you to cut and paste. For this query, it looked like this:

SELECT people.FIRST, people.LAST, people.EMAIL
FROM people.csv people
WHERE (people.LAST='Eckel') AND 
(people.EMAIL Is Not Null)
ORDER BY people.FIRST

With more complicated queries it’s easy to get things wrong, but with a query tool you can interactively test your queries and automatically generate the correct code. It’s hard to argue the case for doing this by hand.

Step 5: Modify and paste in your query

You’ll notice that the code above looks different from what’s used in the program. That’s because the query tool uses full qualification for all of the names, even when there’s only one table involved. (When more than one table is involved, the qualification prevents collisions between columns from different tables that have the same names.) Since this query involves only one table, you can optionally remove the “people” qualifier from most of the names, like this:
SELECT FIRST, LAST, EMAIL
FROM people.csv people
WHERE (LAST='Eckel') AND 
(EMAIL Is Not Null)
ORDER BY FIRST

In addition, you don’t want this program to be hard coded to look for only one name. Instead, it should hunt for the name given as the command-line argument. Making these changes and turning the SQL statement into a dynamically-created String produces:

"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE " +
"(LAST='" + args[0] + "') " +
" AND (EMAIL Is Not Null) " +
"ORDER BY FIRST");

SQL has another way to insert names into a query called stored procedures, which is used for speed. But for much of your database experimentation and for your first cut, building your own query strings in Java is fine.

You can see from this example that by using the tools currently available—in particular the query-building tool—database programming with SQL and JDBC can be quite straightforward.

A GUI version of the lookup program

It’s more useful to leave the lookup program running all the time and simply switch to it and type in a name whenever you want to look someone up. The following program creates the lookup program as an application/applet, and it also adds name completion so the data will show up without forcing you to type the entire last name:

//: c15:jdbc:VLookup.java
// GUI version of Lookup.java.
// <applet code=VLookup
// width=500 height=200></applet>
import javax.swing.*; 
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.sql.*;
import com.bruceeckel.swing.*;

public class VLookup extends JApplet {
  String dbUrl = "jdbc:odbc:people";
  String user = "";
  String password = "";
  Statement s;
  JTextField searchFor = new JTextField(20);
  JLabel completion = 
    new JLabel("                        ");
  JTextArea results = new JTextArea(40, 20);
  public void init() {
    searchFor.getDocument().addDocumentListener(
      new SearchL());
    JPanel p = new JPanel();
    p.add(new Label("Last name to search for:"));
    p.add(searchFor);
    p.add(completion);
    Container cp = getContentPane();
    cp.add(p, BorderLayout.NORTH);
    cp.add(results, BorderLayout.CENTER);
    try {
      // Load the driver (registers itself)
      Class.forName(
        "sun.jdbc.odbc.JdbcOdbcDriver");
      Connection c = DriverManager.getConnection(
        dbUrl, user, password);
      s = c.createStatement();
    } catch(Exception e) {
      results.setText(e.getMessage());
    }
  }
  class SearchL implements DocumentListener {
    public void changedUpdate(DocumentEvent e){}
    public void insertUpdate(DocumentEvent e){
      textValueChanged();
    }
    public void removeUpdate(DocumentEvent e){
      textValueChanged();
    }
  }
  public void textValueChanged() {
    ResultSet r;
    if(searchFor.getText().length() == 0) {
      completion.setText("");
      results.setText("");
      return;
    }
    try {
      // Name completion:
      r = s.executeQuery(
        "SELECT LAST FROM people.csv people " +
        "WHERE (LAST Like '" +
        searchFor.getText()  + 
        "%') ORDER BY LAST");
      if(r.next()) 
        completion.setText(
          r.getString("last"));
      r = s.executeQuery(
        "SELECT FIRST, LAST, EMAIL " +
        "FROM people.csv people " +
        "WHERE (LAST='" + 
        completion.getText() +
        "') AND (EMAIL Is Not Null) " +
        "ORDER BY FIRST");
    } catch(Exception e) {
      results.setText(
        searchFor.getText() + "\n");
      results.append(e.getMessage());
      return; 
    }
    results.setText("");
    try {
      while(r.next()) {
        results.append(
          r.getString("Last") + ", " 
          + r.getString("fIRST") + 
          ": " + r.getString("EMAIL") + "\n");
      }
    } catch(Exception e) {
      results.setText(e.getMessage());
    }
  }
  public static void main(String[] args) {
    Console.run(new VLookup(), 500, 200);
  }
} ///:~

Much of the database logic is the same, but you can see that a TextListener is added to listen to the TextField, so that whenever you type a new character it first tries to do a name completion by looking up the last name in the database and using the first one that shows up. (It places it in the completion Label, and uses that as the lookup text.) This way, as soon as you’ve typed enough characters for the program to uniquely find the name you’re looking for, you can stop.

Why the JDBC API
seems so complex

When you browse the online documentation for JDBC it can seem daunting. In particular, in the DatabaseMetaData interface—which is just huge, contrary to most of the interfaces you see in Java—there are methods such as dataDefinitionCausesTransactionCommit( ), getMaxColumnNameLength( ), getMaxStatementLength( ), storesMixedCaseQuotedIdentifiers( ), supportsANSI92IntermediateSQL( ), supportsLimitedOuterJoins( ), and so on. What’s this all about?

As mentioned earlier, databases have seemed from their inception to be in a constant state of turmoil, primarily because the demand for database applications, and thus database tools, is so great. Only recently has there been any convergence on the common language of SQL (and there are plenty of other database languages in common use). But even with an SQL “standard” there are so many variations on that theme that JDBC must provide the large DatabaseMetaData interface so that your code can discover the capabilities of the particular “standard” SQL database that it’s currently connected to. In short, you can write simple, transportable SQL, but if you want to optimize speed your coding will multiply tremendously as you investigate the capabilities of a particular vendor’s database.

This, of course, is not Java’s fault. The discrepancies between database products are just something that JDBC tries to help compensate for. But bear in mind that your life will be easier if you can either write generic queries and not worry too much about performance, or, if you must tune for performance, know the platform you’re writing for so you don’t need to write all that investigation code.

There is more JDBC information available in the electronic documents that come as part of the Java 1.1 distribution from Sun. In addition, you can find more in the book JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley, 1997). Other JDBC books are appearing regularly.

A more sophisticated JDBC Example

If you’re connecting across a network, the database URL will contain the connection information and the dbUrl can become a bit intimidating. Here is an example from a CloudScape database being called from a remote client utilizing RMI:

jdbc:rmi://192.168.170.27:1099/jdbc:cloudscape:db

This database URL is really two jdbc calls in one. The first part "jdbc:rmi://192.168.170.27:1099/" uses RMI to make the connection to the remote database engine listening on port 1099 at IP Address 192.168.170.27. The second part of the URL, " jdbc:cloudscape:db" conveys the more typical settings using the subprotocol and database name but this will only happen after the first section has made the connection via RMI to the remote machine.

When you’re ready to connect to the database, you call the static method DriverManager.getConnection( ), passing it the database URL, the user name, and a password to get into the database. You get back a Connection object that you can then use to query and manipulate the database.

At this point we should dive into some example code. Our database specific code will reside in the class DBStuff. Our database URL, JDBC driver, username and password will have access methods, all other SQL statements will be public strings.

//: c15:jdbc:DBStuff.java
// A class to hold all our
// database specific code
// for the community
// interests database
import java.sql.*;

public class DBStuff {
  // All the database stuff
  // Specific for CloudScape.
  String dbDriver = 
      "COM.cloudscape.core.JDBCDriver";
  String dbURL =
      "jdbc:cloudscape:d:/docs/_work/JSapienDB";
  String user = "";
  String password = "";
  public String dropMemTbl = "drop table MEMBERS";
  public String createMemTbl = 
       "create table MEMBERS " +
       "(MEM_ID INTEGER primary key, " +
       "MEM_UNAME VARCHAR(12) not null unique, " +
       "MEM_LNAME VARCHAR(40), " +
       "MEM_FNAME VARCHAR(20), " +
       "ADDRESS VARCHAR(40), " +
       "CITY VARCHAR(20), " +
       "STATE CHAR(4), " +
       "ZIP CHAR(5), " +
       "PHONE CHAR(12), " +
       "EMAIL VARCHAR(30))";
  public String createMemIdx = 
       "create unique index " +
       "LNAME_IDX on MEMBERS(MEM_LNAME)";

  public String dropEvtTbl = "drop table EVENTS";
  public String createEvtTbl = 
       "create table EVENTS " +
       "(EVT_ID INTEGER primary key, " +
       "EVT_TITLE VARCHAR(30) not null, " +
       "EVT_TYPE VARCHAR(20), " +
       "LOC_ID INTEGER, " +
       "PRICE DECIMAL, " +
       "DATETIME TIMESTAMP)";
  public String createEvtIdx = 
       "create unique index " +
       "TITLE_IDX on EVENTS(EVT_TITLE)";

  public String dropEMTbl = "drop table EVTMEMS";
  public String createEMTbl = 
       "create table EVTMEMS " +
       "(MEM_ID INTEGER not null, " +
       "EVT_ID INTEGER not null, " +
       "MEM_ORD INTEGER)";
  public String createEMIdx = 
       "create unique index " +
       "EVTMEM_IDX on EVTMEMS(MEM_ID, EVT_ID)";

  public String dropLocTbl = "drop table LOCATIONS";
  public String createLocTbl = 
       "create table LOCATIONS " +
       "(LOC_ID INTEGER primary key, " +
       "LOC_NAME VARCHAR(30) not null, " +
       "CONTACT VARCHAR(50), " +
       "ADDRESS VARCHAR(40), " +
       "CITY VARCHAR(20), " +
       "STATE VARCHAR(4), " +
       "ZIP VARCHAR(5), " +
       "PHONE CHAR(12), " +
       "DIRECTIONS VARCHAR(4096))";
  public String createLocIdx = 
       "create unique index " +
       "NAME_IDX on LOCATIONS(LOC_NAME)";

  public String getDriver() {
    return dbDriver;
  }
  public String getDbURL() {
    return dbURL;
  }
  public String getUser() {
    return user;
  }
  public String getPassword() {
    return password;
  }
} ///:~

Our DBStuff class will generate a set of tables that will have a structure as shown below. Certainly not elaborate but just right for the level of detail we would like to show. There are numerous books, seminars and software packages that will help you in the design and development of a database. We do not want to elaborate on those areas, we will focus primarily on Java. Our goal is to provide a simple example to test most of our Enterprise APIs and a structure that could be easily transferred to the database you use.


The following class, CreateTables uses the DBStuff class to load the JDBC driver, make a connection to the database, then create the table structure outlined above. Once the connection is made and SQL statements have been written there is not much to do except push the SQL to the database and or handle errors responsibily.

//: c15:jdbc:CreateTables.java
// Creates database tables for 
// community interests database
import java.sql.*;

public class CreateTables {

  public static void main(String[] args) {
    DBStuff db = new DBStuff();
    try {
      // Load the driver (registers itself)
      Class.forName(db.getDriver());
    } catch(java.lang.ClassNotFoundException e) {
      System.err.print("ClassNotFoundException: ");
      e.printStackTrace();
    }
    try {
      Connection c = DriverManager.getConnection(
        db.getDbURL(),
        db.getUser(),
        db.getPassword());
      Statement s = c.createStatement();
      // SQL code:
      // Create the MEMBERS table
      try {
        s.executeUpdate( db.dropMemTbl );
      } catch(SQLException sqlEx) {
        String msg;
        msg = "Table MEMBERS not present. " +
              "Drop failed.";
        System.out.println(msg);
      } 
      s.executeUpdate( db.createMemTbl );
      s.executeUpdate( db.createMemIdx );
      
      // Create the EVENTS table
      try {
        s.executeUpdate( db.dropEvtTbl );
      } catch(SQLException sqlEx) {
        String msg;
        msg = "Table EVENTS not present. " +
              "Drop failed.";
        System.out.println(msg);
      } 
      s.executeUpdate( db.createEvtTbl );
      s.executeUpdate( db.createEvtIdx );
      
      // Create the EVTMEMS table
      try {
        s.executeUpdate( db.dropEMTbl );
      } catch(SQLException sqlEx) {
        String msg;
        msg = "Table EVTMEMS not present. " +
              "Drop failed.";
        System.out.println(msg);
      } 
      s.executeUpdate( db.createEMTbl );
      
      // Create the LOCATIONS table
      try {
        s.executeUpdate( db.dropLocTbl );
      } catch(SQLException sqlEx) {
        String msg;
        msg = "Table LOCATIONS not present. " +
              "Drop failed.";
        System.out.println(msg);
      } 
      s.executeUpdate( db.createLocTbl );
      s.executeUpdate( db.createLocIdx );
      
      s.close();
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

You can see the creation of the database URL as previously described. In this example, there is no password protection on the database so the user name and password are empty strings.

Once the connection is made with DriverManager.getConnection( ), you can use the resulting Connection object to create a Statement object using the createStatement( ) method. With the resulting Statement, you can call executeUpdate( ), passing in a string containing an SQL-92 standard SQL statement. (You’ll see shortly how you can generate this statement automatically, so you don’t have to know much about SQL.) executeUpdate( ) usually will return the number of rows that were affected by the SQL statement. executeUpdate( ) is more commonly used to execute INSERT, UPDATE, or DELETE statements that modify one or more rows. For statements such as CREATE TABLE, DROP TABLE and CREATE INDEX, executeUpdate( ) always returns zero.

The Statement interface provides two other methods for executing SQL statements: executeQuery( ) and execute( ). The correct method to use is determined by what the SQL statement produces.

The executeQuery( ) method returns a ResultSet object, which is quite a bit like an iterator: the next( ) method moves the iterator to the next record in the statement, or returns false if the end of the result set has been reached. You’ll always get a ResultSet object back from executeQuery( ) even if a query results in an empty set (that is, an exception is not thrown). Note that you must call next( ) once before trying to read any record data. If the result set is empty, this first call to next( ) will return false. For each record in the result set, you can select the fields using (among other approaches) the field name as a string. Also note that the capitalization of the field name is ignored—it doesn’t matter with an SQL database. You determine the type you’ll get back by calling getInt( ), getString( ), getFloat( ), etc. At this point, you’ve got your database data in Java native format and can do whatever you want with it using ordinary Java code.

The method execute( ) is used to execute statements that return more than one result set, more than one update count, or a combination of the two.

Let's now load the table we created above with data. This will require us to perform a series of INSERTS followed by a SELECT to see all the data in our table and to let use exercise a result set.

Servlets

Now that we understand JDBC and how it abstracts all those different database backends into a common API set, let’s turn our attention to another common task - handling HTTP requests and responses. It is taken for granted in today’s technical environment that client access from the Internet or corporate intranets is a sure way to allow many users to access data and resources easily. This type of access is predicated on the clients utilizing the World Wide Web standards of Hypertext Markup Language (HTML) and Hypertext Transfer Protocol (HTTP). Wouldn’t it be nice to have an API set that abstracted out this commonly used area of client access? Welcome Java Servlets!

Traditionally, the way to handle a problem such as allowing an Internet client to update their personal data is to create an HTML page with a text field and a “submit” button. The user can type whatever he or she wants into the text field, and it will be submitted to the server without question. As it submits the data, the Web page also tells the server what to do with the data by mentioning the Common Gateway Interface (CGI) program that the server should run after receiving this data. This CGI program is typically written in either Perl or C (and sometimes C++, if the server supports it), and it must handle everything. First it looks at the data and decides whether it’s in the correct format. If not, the CGI program must create an HTML page to describe the problem; this page is handed to the server, which sends it back to the user. The user must then back up a page and try again. If the data is correct, the CGI program opens the data file and either adds the email address to the file or discovers that the address is already in the file. In both cases it must format an appropriate HTML page for the server to return to the user.

As Java programmers, this seems like an awkward way for us to solve the problem, and naturally, we’d like to do the whole thing in Java. First, we’ll use a Java applet to take care of data validation at the client site, without all that tedious Web traffic and page formatting. Then let’s skip the Perl CGI script in favor of a Java application running on the server. In fact, let’s skip the Web server altogether and simply make our own network connection from the applet to the Java application on the server!

As you’ll see, there are a number of issues that make this a more complicated problem than it seems. It would be ideal to write the applet but applet’s, while a proven technology with plenty of support, have been problematic in the Wild World Web where different browsers handle applet’s differently. In a corporate intranet where there is some level of standardization this seems possible in the short term but what happens with the next acquisition or merger? What happens when employees want to start working from home? That’s easy - things start to look a lot like the Internet - you can’t depend on anything especially how applets are implemented in your client’s browsers. So to be on the safe side, what we really want to do is deal with straight HTML and HTTP within our java server. The client knows nothing of the implementation, they are only aware that they can get at their data and perform their work without installing, upgrading or calling tech support.

Sun has delivered on this need. The Servlet API wraps up the HTTP protocol so we can put Java on the server side of our HTTP connection and deal with our client in HTML and HTTP. Servlets are completely server side java for the Web. The client sees nothing except HTTP and HTML.

The basic servlet

The architecture of the Servlet API is that of a classic service provider with a service( ) method through which all client requests will drive and life cycle methods init( ) and destroy( ).

public interface Servlet {
    public void init(ServletConfig config)
      throws ServletException;
    public ServletConfig getServletConfig();
    public void service(ServletRequest req,
      ServletResponse res) 
      throws ServletException, IOException;
    public String getServletInfo();
    public void destroy();
}

getServletConfig( ) sole purpose is to return a ServletConfig object which contains initialization and startup parameters for this servlet and getServletInfo( ) returns a string containing information about the servlet, such as author, version, and copyright.

The GenericServlet class is a shell implementation of this interface, nothing more. The HttpServlet class is an extension of GenericServlet and is designed specifically to handle the HTTP protocol.

Although you can derive your servlets from GenericServlet, Sun recommends that all servlets derive from HttpServlet. This makes sense since your servlet is designed to work with a servlet engine that is satisfying clients requests from within a Web server. Why not utilize the built-in parsing capabilities for POST and GET that come with HttpServlet? We will be getting into this shortly.

The most wonderful attribute of the Servlet API is the auxiliary objects that come along with the HttpServlet class to support it. Look at the service( ) method in the Servlet interface. It has two parameters ServletRequest and ServletResponse. With the HttpServlet class these two object are extended for HTTP as well—HttpServletRequest and HttpServletResponse. Let’s take a closer look.

//: c15:servlets:ServletsRule.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class ServletsRule extends HttpServlet {
  int i = 0; // Servlet "persistence"
  public void service(HttpServletRequest req, 
  HttpServletResponse res) throws IOException {
    PrintWriter out = res.getWriter();
    out.print("<HEAD><TITLE>");
    out.print("A server-side strategy");
    out.print("</TITLE></HEAD><BODY>");
    out.print("<h1>Servlets Rule! " + i++);
    out.print("</h1></BODY>");  
    out.close();    
  }
} ///:~

ServletsRule is about as simple as a servlet can get. But that is the beauty of it all - just think how much stuff is being handled for us! Once the servlet is initialized - its init( ) method has run to completion - can clients enter the service( ) method. In the service method our main responsibility is to interact with the HTTP request the client sent us and build a HTTP response based upon the attributes contained within the request. In ServletsRule we only manipulate the response object without looking at what the client may has sent us. We call the getWriter( ) method of the response object to get a PrintWriter object. The PrintWriter is used for writing character-based response data.

As we dive more deeply into the HttpRequest and HttpResponse objects you should notice that a greater understanding of HTTP and HTML would be helpful. Servlets are designed for Web server-side development and it shows. Now let’s scratch a little deeper by getting some HTML form data that was passed to the servlet in the request object.

//: c15:servlets:EchoForm.java
// Dumps the name-value pairs of any HTML form
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class EchoForm extends HttpServlet {
  public void doGet(HttpServletRequest req, 
    HttpServletResponse res) throws IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();
    out.print("<h1>Your form contained:</h1>");
    Enumeration flds = req.getParameterNames();
    while(flds.hasMoreElements()) {
      String field = (String)flds.nextElement();
      String value = req.getParameter(field);
      out.print(field + " = " + value + "<br>");
    }
    out.close();    
  }
  public void doPost(HttpServletRequest req, 
    HttpServletResponse res) throws IOException {
    doGet(req, res);
  }  
} ///:~

The EchoForm servlet parrots back to our client the request fields that have been sent to the servlet as part of the request object. Since the request fields will be placed into a response we must set up the response object with calls to setContentType( ) and getWriter( ). In an HttpResponse object the setContentType( ) methods sets the Content-Type HTTP header. This is most commonly "text/html".

Notice that in EchoForm the service( ) method has been replaced by doGet( ). This automatic HTTP method name parsing is a feature of HttpServlet. HTTP was designed for the Web and has been made more general than necessary. The first word on the full request line is simply the name of the method (command) to be executed on the Web page. The built-in methods are GET, POST, HEAD, PUT, DELETE, LINK and UNLINK. The HttpServlet class is written to parse these methods and let the programmer react differently for a GET than a POST or even a HEAD request method. If you don't care you can just override service( ). Since most HTTP methods are POST or GET many times you just implement one and direct the other to it.

In The HTTP request object has the potential to come with request parameters and generally does. The parameters are typically name-value pairs sent as part of its query string (for GET requests) or as encoded post data (for POST requests). EchoForm uses the request object's getParameterNames( ) method to loop through the parameter list. The getParameter( ) method is used to pull the value. The pair is then written to the PrintWriter of the response object with the appropriate HTML tags. The response object is sent to the client when the servlet is finished.

Servlets and Multithreading

Now that you understand the basics you should be realizing that servlets are excellent for server-side Web development. Elegant and straight forward, they do just about everything for you - right? Well, almost. Remember early we said all client requests drive through the service method? This is the well-used, high traffic corridor of the servlet and more than one client request may come through at the same time. The servlet engine has a pool of threads that it will dispatch to handle client requests. It is quite likely that two clients arriving at the same time could beprocessing through your service( ) or doGet( ) or doPost( ) methods at the same time. Therefore the service( ) methods and other methods called by HttpServlet.service( ) (e.g., doGet( ), doPost( ), doHead( ),etc.) need to be written in a thread-safe manner. Any common resources (files, databases) that will be used by your client requests will need to be synchronized.

ThreadServlet is a simple example that simply synchronizes around the threads sleep( ) method. This will hold up all threads until the allotted time (5000 ms) is all used up. When testing this you should start several browsers instances and hit this servlet as quickly as possible.

//: c15:servlets:ThreadServlet.java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class ThreadServlet extends HttpServlet {
  int i;
  public void service(HttpServletRequest req, 
    HttpServletResponse res) throws IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();
    synchronized(this) {
      try {
        Thread.currentThread().sleep(5000);
      } catch(InterruptedException e) {}
    }
    out.print("<h1>Finished " + i++ + "</h1>");
    out.close();    
  }
} ///:~


Handling Sessions with Servlets

The servlet API comes with more than just the classes that implement the servlet interface, GenericServlet and HttpServlet and the Request and Response objects. The design of HTTP is such that it is a 'sessionless' protocol. A great deal of effort has gone into mechanisms that will allow Web developers to track sessions. How could companies do e-commerce if you couldn't keep track of client and the items they have put into their shopping cart? You couldn't! This may be great for privacy advocates but it does little to help create robust, commerce driven Web sites.

There are several methods of session tracking but the most common method is with persistant 'cookies'. The term cookie sounds cute and could be perceived as a session tracking solution that was baked up in someone's garage. The fact is that cookies are an integral part of the Internet standards. The HTTP Working Group of the Internet Engineering Task Force has written cookies in the official standard in RFC 2109 (http://ds.internic.net/rfc/rfc2109.txt or check http://www.cookiecentral.com).

A cookie is nothing more than a small piece of information sent by a Web server to a browser. The browser stores the cookie locally and all calls to the server from that browser will contain the cookie as an identifier. The cookie therefore acts to uniquely identify the client with each hit of this Web server. It should be noted that clients can turn off the browsers ability to accept cookies. If your site still need to be able to session track this type of client then another method of session tracking (URL rewriting or hidden form fields) will have to be incorporated. The session tracking capabilities built into the Servlet API are designed around cookies.

The Cookie Class

The Servlet API (version 2.0 and up) provides the javax.servlet.http.Cookie class. This class incorporates all the HTTP header details and allows the setting of various cookie attributes. Using the cookie is simply a matter of creating it using the constructor and adding it to the response object. The constructor takes a cookie name as the first argument and a value as the second. Cookies are added to the response object before you send any content.

Cookie oreo = new Cookie("TIJava", "2000");
res.addCookie(cookie);

Cookies are then received by calling the getCookies( ) method of the HttpServletRequest object which returns an array of cookie objects.

Cookie[] cookies = req.getCookies();

The Session Class

A session in the world of HTTP and the Internet is one or more page requests by a client to a Web site during a defined period of time. If I am buying my groceries on-line, I want a session to be confined to the period from when I first add an item to my shopping cart to the point where I checkout. Each item I add to the shopping cart will be a new connection in the HTTP world, they have no knowledge of previous connections or items in the shopping cart. The mechanics supplied by the Cookie specification allows us to perform 'session tracking'.

You should understand that a cookie is an object that encapsulates that small bit of information that will be stored on the client side. A Servlet Session object lives on the server side of the communication channel and its goal is to capture data about this client that would be useful as the client moves through and interacts with your Web site. This data may be pertinent for the present session, such as items in the shopping cart or it may be information you asked the client to enter such as authentication information entered when the client first entered your Web site and which should not have to be re-enter before a set time of inactivity.

The Session class of the Servlet API uses the Cookie class but really all the session object needs is a unique identifier stored on the client and passed to the server. Usually this is a cookie and that is the mechanism we will cover here. Web sites may also use the other types of session tracking but these mechanisms will be more difficult to implement as they are not encapsulated into the Servlet API.

Let's take a look at implementing session tracking with the Servlet API:

//: c15:servlets:SessionPeek.java
// Using the HttpSession class.
import java.io.*;
import java.util.Date;
import javax.servlet.*;
import javax.servlet.http.*;

public class SessionPeek extends HttpServlet { 
  public void doGet(HttpServletRequest req, 
  HttpServletResponse res)
  throws ServletException, IOException {
    // Retrieve Seesion Object before any
    // output is sent to the client.
    HttpSession session = req.getSession();
    // Get the output stream
    ServletOutputStream out =
      res.getOutputStream();
    res.setContentType("text/html");
    out.println("<HEAD><TITLE> SessionPeek ");
    out.println(" </TITLE></HEAD><BODY>");
    out.println("<h1> SessionPeek </h1>");
    // A simple hit counter for this session.
    Integer ival = (Integer) 
      session.getValue("sesspeek.cntr");
    if(ival==null) 
      ival = new Integer(1);
    else 
      ival = new Integer(ival.intValue() + 1);
    session.putValue("sesspeek.cntr", ival);
    out.println("You have hit this page <b>"
      + ival + "</b> times.<p>");
    // Session Data
    out.println("<h2>");
    out.println(" Saved Session Data </h2>");
    // loop through all data in the session
    // and spit is out.
    String[] sesNames = session.getValueNames();
    for(int i = 0; i < sesNames.length; i++) {
      String name = sesNames[i]; 
      String value = 
        session.getValue(name).toString();
      out.println(name + " = " + value + "<br>");
    }
    // Session Statistics    
    out.println("<h3> Session Statistics </h3>");
    out.println("Session ID: " 
      + session.getId() + "<br>");
    out.println("New Session: " + session.isNew()
      + "<br>");
    out.println("Creation Time: "
      + session.getCreationTime());
    out.println("<I>(" + 
      new Date(session.getCreationTime())
      + ")</I><br>");
    out.println("Last Accessed Time: " +
      session.getLastAccessedTime());
    out.println("<I>(" +
      new Date(session.getLastAccessedTime())
      + ")</I><br>");
    out.println("Session Inactive Interval: "
      + session.getMaxInactiveInterval());
    out.println("Session ID in Request: "
      + req.getRequestedSessionId() + "<br>");
    out.println("Is session id from Cookie: "
      + req.isRequestedSessionIdFromCookie()
      + "<br>");
    out.println("Is session id from URL: "
      + req.isRequestedSessionIdFromURL()
      + "<br>");
    out.println("Is session id valid: "
      + req.isRequestedSessionIdValid()
      + "<br>");
    out.println("</BODY>");
    out.close();
  }
  public String getServletInfo() {
    return "A session tracking servlet";
  }
} ///:~

The very first thing we do when we enter the doGet( ) method is to call getSession( ) on the request object. getSession will return the session object associated with this request. Do not be misled into thinking that the session object is returned with the request. The session object does not travel across the network it lives on the server and is associated with a client and its requests.

getSession( ) now comes in two versions—no parameter as used here and getSession(boolean). getSession(true) is equivalent to getSession( ). The only reason for the boolean is to state whether you want to the session object created if it is not found. getSession(true) is the most likely call hence getSession( ).

The session object, if it is not new, will give us details about our client on their previous visits. If the session object is new then we will start with this visit to gather information about this client’s activities. Capturing this client information is done through the setAttribute( ) and getAttribute( ) methods of the session object.

java.lang.Object getAttribute(java.lang.String)
void setAttribute(java.lang.String name,
                  java.lang.Object value)

The session object uses a simple name-value pairing for loading information. The values must be derived from java.lang.Object and the name is a string. In ServletPeek we are keeping track of how many times the client has been back here during this session. This is done with an Integer object that is named sesspeek.cntr. You should notice in the if-else statement that if the name is not found we create a new Integer with value of 1, otherwise we create a new Integer with a value equal to the incremented value of the previously held Integer. We stick the new Integer into the session object and let the garbage collector handle the old Integer. You should realize that if you are using the same key the new object would overwrite the old one. Lastly, we use our incremented counter to display how many times the client has visited during this session.

Related to getAttribute( ) and setAttribute( ) is getAttributeNames( ). getAttributeNames( ) returns an enumeration of all the names of objects that are bound to the session object. This is quite handy and a small while loop has been added to SessionPeek to show this method in action.

This brings us to the question “Just how long does a session object hang around for?” The answer depends upon the servlet engine you are using although I think they usually default to 30 minutes (1800 seconds), which is what you should see from the ServletPeek call to getMaxInactiveInterval( ). We have tested this and found mixed results between servlet engines. Sometimes the session object can hang around overnight. I have never seen a case where the session object disappears before the Inactive Interval. You can try this by setting the Inactive Interval with setMaxInactiveInterval( ) to 5 seconds and see if your session object hangs around or if it is cleaned up at the appropriate time. This may be an attribute you will want to investigate while choosing a servlet engine.

Getting the Servlet examples to work

If you are not already working with an application server that handles Sun's Servlet and JSP technologies for you, then you may want to reevaluate your choice of application server or you may want to download the Tomcat implementation of Java Servlets and JSP’s. This can be found at http://jakarta.apache.org.

First, you should follow the instructions for decompressing the version specific to your environment. This will install the Tomcat implementation into a directory structure under where you unzipped it. Second, edit the server.xml so that you have a new Web application. Lastly, you will then want to load the servlet examples into that new Web application directory where the Tomcat server can find them.

Java Server Pages

Servlets are found by the servlet engine looking for the .class file along a Web application path defined by the configuration of the Web server. That makes sense, no problem there. But what if we would like the Web server to compile the servlet at the time it is invoked, thereby insuring that the lastest and greatest code is delivered to the client. This is the nature of JavaServer Pages or JSPs.

You can think of JSPs as special Java tags inside the HTML page that will result in a servlet being generated, then compiled by the Web server at the time the client invokes that page. This allows the separation of the dynamic content and the static content in the HTML page. You get support for scriping and tags plus the reuse associated with those tags and the JSP components that provide the functionality. Essentially, your previously static HTML pages have become dynamic in some of it parts.

The structure of a JSP page is a cross between a servlet and an HTML page. The JSP page is a test-based document that describes how to process a request and create a response. The text based description of the page intermixes template data with some dynamic actions and leverages the Java platform. JavaServer Pages is a standard extension that is defined on top of the Servlet Standard Extension. JSP 1.1 uses class from Servlet 2.2 which relies on JRE 1.1.

Here’s an extremely simple JSP example that uses a standard Java library call to get the current time in milliseconds, which is then divided by 1000 to produce the time in seconds. Since a JSP expression (the <%= ) is used, the result of the calculation is coerced into a String so it can be printed to the out object (which puts it in the Web page):

//:! c15:jsp:ShowSeconds.jsp
<html><body>
<H1>The time in seconds is: 
<%= System.currentTimeMillis()/1000 %></H1>
</body></html>
///:~

There is a great deal more going on here than meets the eye. If we follow the route of the request and response we can get an idea how much many layers the request and response are moving through. The client creates the request for the JSP page and sends it off to the Web Server. The Web Server must be able to find the JSP page and forward the request to the page. The JSP page has associated with it a compiled component that is created from the Java code embedded within. This JSP component is the ultimate destination of the request and the creator of the response. The response then bubbles up the same path that the request followed picking up pieces to pass back to the client along the way.

This is important as Sun describes a JSP page as “a text-based document that describes how to process a request and create a response.” That about a broad as you can get so let’s dig deeper.


Basic operations

We know that the server automatically creates, compiles, loads and runs a special servlet to generate the page’s content. The static portions of the HTML page are generated by the servlet using the equivalent of out.println( ) calls within the servlet. The dynamic portions are included directly into the servlet.

There is good and bad in everything and JSPs are no different. The downside to all this dynamism is poor performance for first time access. Try it—it is obvious. The first access is slow and subsequent accesses are excellent.

Implicit Objects

When we looked at servlets there were several objects already built into the API—response, request, session, etc. These objects are very conveniently built into the JSP specification and they provide the same robust foundation for manipulating HTTP and HTML in a Web application.

JSP writers have access to these implicit objects within the JSP page just as you would within a servlet. The implicit objects in a JSP are detailed in the table below. Each of the variables has a class or interface that is defined in the core Java technology or the Java Servlet API. Scope of each object can vary significantly. For example, a Session object would have a scope exceeding that of a page as it many span several client requests and pages and an application object would provide service to a group of jsp pages that together would represent a Web application.

Implicit variable

Of Type (javax.servlet)

Description

Scope

request

protocol dependent subtype of : HttpServletRequest.

The request that triggers the service invocation.

request

response

protocol dependent subtype of : HttpServletResponse.

The response to the request.

page

pageContext

jsp.PageContext

The page context encapsulates implementation-dependent features and provides convenience methods and namespace access for this JSP.

page

session

protocol dependent subtype of: http.HttpSession

The session object created for the requesting client. See Servlet Session object.

session

application

ServletContext

The servlet context obtained from the servlet configuration object (e.g., getServletConfig( ).getContext( )

app

out

jsp.JspWriter

The object that writes into the output stream.

page

config

ServletConfig

The ServletConfig for this JSP.

page

page

java.lang.Object

The instance of this page’s implementation class processing the current request.

page





JSP Scripting Elements

The implicit objects and the power of Java are all brought together with JSP actions. Actions affect the current out stream and use, modify or create objects. The actions to be performed will be determined by the details of the request object received by the JSP page. The JSP specification includes actions types that are standard and must be implemented by conforming engines. The syntax for action elements is based on XML.

The actions all start with Directives. Directives are messages to the JSP engine and the syntax is:

<%@ directive {attr=”value”}*%>

Directives do not produce any output into the current out stream but they are important in setting up your JSP pages attributes and dependencies with the JSP engine. As an example the line:

<%@ page language=”java” %>

says that the scripting language being used within the JSP page is Java. In fact the specification only describes the semantics of scripts for the language attribute equal to Java. This should give you an idea of the flexibility that is being built into the JSP technology. In the future, if you were to choose another language, say Python (a good scripting choice), then that language would have to support the Java Run-time Environment by exposing the Java technology object model to the script environment, especially the implicit variables defined above, JavaBeans properties, and public methods.

The most important directive is the page directive. It defines a number of page dependent attributes and communications these attributes to the JSP engine. These attributes include: language, extends, import, session, buffer, autoFlush, isThreadSafe, info and errorPage. For example:

<%@ page session=”trueimport=”java.util.*” %>

This line indicates that the page requires participation in an (HTTP) session. Since we have not set the language directive the JSP engine defaults to java and the implicit script language variable named “session” is of type javax.servlet.http.HttpSession. If the directive had been false then the implicit variable “session” would be unavailable, the default is true.

The import attribute describes the types that are available to the scripting environment. This attribute is used just as it would be in the Java programming language i.e. a (comma separated) list of either a fully qualified Java type name denoting that type, or of a package named followed by the “.*” string denoting all the public types declared in that package. The import list is imported by the translated JSP page implementation and is available to the scripting environment. Again, this is currently only defined for when the value of the language directive is “java”.

Once the directives have been used to set the scripting environment we can utilize the scripting language elements. JSP 1.1 has three scripting language elements—declarations, scriptlets, and expressions. A declaration will declare elements, a scriptlet is a statement fragment, and an expression is a complete language expression. In JSP each scripting element begins with a “<%”. The exact syntax for each is:

<%! declaration %>
<%  scriptlet   %>
<%= expression  %>

White space is optional after “<%!”, “<%”, “<%=”, and before “%>”.

As mentioned earlier, all these tags are based upon XML. More accurately you could state that a JSP page could be mapped to a XML document and although this is a little touted section of the specification, I suspect you will be hearing more and more about this aspect of JSP as Java, XML, and server-side Java become more intertwined. I will not go into these mapping details here but you should be aware of them and if you need more details you should refer to the JSP specification. Therefore, you should realize that the XML equivalent syntax for the scripting elements above would be:

<jsp:declaration> declaration </jsp:declaration>
<jsp:scriptlet>   scriptlet   </jsp:scriptlet>
<jsp:expression>  expression  </jsp:expression>

Declarations are used to declare variables and methods in the scripting language used in a JSP page—Java at this time. The declaration should be a complete Java statement and should not produce any output in the current out stream. In the Hello.jsp example below the variables loadTime, loadDate and hitCount are all complete Java statements declares new variables and initializes them.

//:! c15:jsp:Hello.jsp
<%-- This JSP comment will not appear in the
generated html --%>
<%-- This is a JSP directive: --%>
<%@ page import="java.util.*" %>
<%-- These are declarations: --%>
<%!
    long loadTime= System.currentTimeMillis();
    Date loadDate = new Date();
    int hitCount = 0;
%>
<html><body>
<%-- The next several lines are the result of a 
JSP expression inserted in the generated html;
the '=' indicates a JSP expression --%>
<H1>This page was loaded at <%= loadDate %> </H1>
<H1>Hello, world! It's <%= new Date() %></H1>
<H2>Here's an object: <%= new Object() %></H2>
<H2>This page has been up 
<%= (System.currentTimeMillis()-loadTime)/1000 %>
seconds</H2>
<H3>Page has been accessed <%= ++hitCount %> 
times since <%= loadDate %></H3>
<%-- A "scriptlet" which writes to the server
console. Note that a ';' is required: --%>
<%
   System.out.println("Goodbye");
   out.println(“Cheerio”);
%>
</body></html>
///:~

At the tail end of Hello.jsp is a scriptlet that writes “Goodbye” to the Web server console and “Cheerio” to the implicit out JspWriter object. Scriptlets can contain any code fragments that are valid Java statements. Scriptlets are executed at request-processing time. When all the scriptlets fragments in a given JSP are combined in the order they appear in the JSP page, they should yield a valid statement as defined by the Java programming language. Whether or not they produce any output into the out stream depends upon the actual code in the scriptlet. You should be careful as scriptlets can have side effects through their modification of the objects visible within them.

JSP expressions can found intermingled with the HTML in the middle section of Hello.jsp. Expressions are interesting because they must be complete Java statements, which are then evaluated. The result of the JSP expression is coerced to a java.lang.String which is emitted into the current implicit out JspWriter object. If the result of the expression cannot be coerced to a java.lang.String then a ClassCastException is thrown.

Extracting fields and values


//:! c15:jsp:DisplayFormData.jsp
<%-- Fetching the data from an HTML form. --%>
<%-- This JSP also generates the form. --%>
<%@ page import="java.util.*" %>
<html><body>
<H1>DisplayFormData</H1><H3>
<%
Enumeration f = request.getParameterNames();
if(!f.hasMoreElements()) { // No fields
%>
<form method="POST" action="DisplayFormData.jsp">
<%
  for(int i = 0; i < 10; i++) {
%>
    Field<%=i%>:
    <input type="text" size="20" 
      name="Field<%=i%>" value="Value<%=i%>"><br>
<% } %>
  <INPUT TYPE=submit name=submit Value="Submit">
</form>
<% } %>
<%
Enumeration flds = request.getParameterNames();
while(flds.hasMoreElements()) {
  String field = (String)flds.nextElement();
  String value = request.getParameter(field);
%>
  <li><%= field %> = <%= value %></li>
<%
} 
%></H3></body></html>
///:~


JSP Page Attributes and Scope

I have spent some time trying to get my code editor to view a .jsp page with syntax highlighting. This is bit more difficult than it would seem. Is a .jsp page Java or is it HTML? That's easy - it's both. So setting up my color coding was like melding the HTML section with the Java section. The real pint is that a .jsp page provides a new set of tags that allows you to separate the passive display code (HTML) from the dynamic programming code (Java).

There is no reason you can't have a whole block of code that performs some action that will provide content for you HTML. This action could be a database call or a call to some other resource. PageContext.jsp below calls getAttributeNamesInScope( ) method of pageContext to get all the attributes in the scope passed in (1 refers to page).

//:! c15:jsp:PageContext.jsp
<%--Viewing the attributes in the pageContext--%>
<%-- Note that you can include any amount of code
inside the scriptlet tags --%>
<%@ page import="java.util.*" %>
<html><body>
<%
  session.setAttribute("My dog", "Ralph");
  for(int scope = 1; scope <= 4; scope++) {  
    out.println("<H3>Scope: " + 
                 scope + "</H3><BR>");
    Enumeration e =
      pageContext.getAttributeNamesInScope(scope);
    while(e.hasMoreElements()) {
      out.println("\t<li>" + 
        e.nextElement() + "</li>");
    }
  }
%>
<H4>End of list</H4>
</body></html>
///:~

The output looks like this:

Scope: 1

javax.servlet.jsp.jspOut

javax.servlet.jsp.jspPage

javax.servlet.jsp.jspSession

javax.servlet.jsp.jspApplication

javax.servlet.jsp.jspPageContext

javax.servlet.jsp.jspConfig

javax.servlet.jsp.jspResponse

javax.servlet.jsp.jspRequest

Scope: 2

org.apache.tomcat.servlet.resolved

Scope: 3

My dog

Scope: 4

sun.servlet.workdir

javax.servlet.context.tempdir

End of list

Scope 1 is the page scope and all objects reference available in this scope will be discarded upon completion of the current request by the page body. Scope 2 refers to the request scope and will be discarded upon completion of the current client request. As you can see I am using the Apache Tomcat implementation of the Servlets and JSP. (I am not sure what org.apache.tomcat.servlet.resolved is I will try to find out.) Scope 3 will be our session scope and the only object we have with session scope is the one that we added right before the for loop - "My dog". Scope 4 is the scope of our application and is based upon the ServletContext object. There is one ServletContext per "Web application" per Java Virtual Machine. (A "Web application" is a collection of servlets and content installed under a specific subset of the server's URL namespace such as /catalog. In the Tomcat release this information is set via server.xml file.) At the application scope level we have to objects that represent paths for working directory and temporary directory.

Manipulating sessions in JSP

Let's take a closer look at sessions within the JSP model. The next example will exercise the session object a little bit and allow you to manipulate the amount of time before your session becomes invalid. First we must capture some information about this session object. I make a call to getID( ), getCreationTime( ) and getMaxInactiveInterval( ) and display these attributes about our session. When I first bring this session up in the Tomcat implementation the MaxInactiveInterval is 1800 seconds or 30 minutes. Now I know a bit about my session, so lets change its behavior by shortening the MaxInactiveInterval to 5 seconds. Now we should see some action. Next, I check to see if the object "My dog" is attached to the session object giving it session scope. The first time through this should be null but right afterwards we do create a String object "Ralph" and attach it to the session object by call setAttribute( ). Now Ralph should hang around for at least 5 seconds. The invalidate button at the bottom calls a second .jsp page SessionObject2.jsp that simply asks the session if it has the object tagged "My dog" then kills the session by calling invalidate( ) on the session object. "Ralph" is gone. The other button on the bottom of SessionObject.jsp is "Keep Around". This calls a third page, SessionObject3.jsp, that does NOT invalidate the session and you can see that "Ralph" in fact does hang around as long as your 5 second time interval does not expire. Try the refresh button on SessionObject.jsp or move back and forth between SessionObject and SessionObject3.jsp (Keep Around button) a couple of times using different intervals to get a feel for how long "My dog" stays around. (For those of you who have kids this is like the Tomagotchi pets - as long as you play with "Ralph" he will stick around otherwise he packs it up :-)

//:! c15:jsp:SessionObject.jsp
<%--Setting and getting session object values--%>
<html><body>
<H1>Session id: <%= session.getId() %></H1>
<H3><li>This session was created at 
<%= session.getCreationTime() %></li></H1>
<H3><li>MaxInactiveInterval= 
  <%= session.getMaxInactiveInterval() %></li></H3>
<% session.setMaxInactiveInterval(5); %>
<H3><li>Reset MaxInactiveInterval= 
  <%= session.getMaxInactiveInterval() %></li></H3>
<H2>If this session object "My dog" is still around <H2>  
<H3><li>Session value for "My dog" =  
<%=
    session.getAttribute("My dog")
%></li></H3>
<%-- Now add the session object "My dog" --%>
<% 
    session.setAttribute("My dog", 
                    new String("Ralph"));
%>
<H1>My dog's name is 
<%= session.getAttribute("My dog") %></H1>
<%-- See if "My dog" wanders to another form --%>
<FORM TYPE=POST ACTION=SessionObject2.jsp>
<INPUT TYPE=submit name=submit Value="Invalidate">
</FORM>
<FORM TYPE=POST ACTION=SessionObject3.jsp>
<INPUT TYPE=submit name=submit Value="Keep Around">
</FORM>
</body></html>
///:~
//:! c15:jsp:SessionObject2.jsp
<%--The session object carries through--%>
<html><body>
<H1>Session id: <%= session.getId() %></H1>
<H1>Session value for "My dog" 
<%= session.getValue("My dog") %></H1>
<% session.invalidate(); %>
</body></html>
///:~
//:! c15:jsp:SessionObject3.jsp
<%--The session object carries through--%>
<html><body>
<H1>Session id: <%= session.getId() %></H1>
<H1>Session value for "My dog" 
<%= session.getValue("My dog") %></H1>
<FORM TYPE=POST ACTION=SessionObject.jsp>
<INPUT TYPE=submit name=submit Value="Return">
</FORM>
</body></html>
///:~


Creating and modifying Cookies

//:! c15:jsp:Cookies.jsp
<%--This program has different behaviors under
 different browsers! --%>
<html><body>
<H1>Session id: <%= session.getId() %></H1>
<%
  Cookie[] cookies = request.getCookies();
  for(int i = 0; i < cookies.length; i++) { %>
Cookie name: <%= cookies[i].getName() %> <br>
value: <%= cookies[i].getValue() %><br>
Max age in seconds: 
  <%= cookies[i].getMaxAge() %><br>
<% cookies[i].setMaxAge(3); %>
Max age in seconds: 
  <%= cookies[i].getMaxAge() %><br>
<% response.addCookie(cookies[i]); %>
<% } %>
<%-- <% response.addCookie(
    new Cookie("Bob", "Car salesman")); %> --%>
</body></html>
///:~


RMI (Remote Method Invocation)

Traditional approaches to executing code on other machines across a network have been confusing as well as tedious and error-prone to implement. The nicest way to think about this problem is that some object happens to live on another machine, and you can send a message to that object and get a result as if the object lived on your local machine. This simplification is exactly what Java 1.1 Remote Method Invocation (RMI) allows you to do. This section walks you through the steps necessary to create your own RMI objects.

Remote interfaces

RMI makes heavy use of interfaces. When you want to create a remote object, you mask the underlying implementation by passing around an interface. Thus, when the client gets a reference to a remote object, what they really get is an interface reference, which happens to connect to some local stub code that talks across the network. But you don’t think about this, you just send messages via your interface reference.

When you create a remote interface, you must follow these guidelines:

  1. The remote interface must be public (it cannot have “package access,” that is, it cannot be “friendly”). Otherwise, a client will get an error when attempting to load a remote object that implements the remote interface.
  2. The remote interface must extend the interface java.rmi.Remote.
  3. Each method in the remote interface must declare java.rmi.RemoteException in its throws clause in addition to any application-specific exceptions.
  4. A remote object passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface, not the implementation class.

Here’s a simple remote interface that represents an accurate time service:

//: c15:rmi:PerfectTimeI.java
// The PerfectTime remote interface.
package c15.rmi;
import java.rmi.*;

interface PerfectTimeI extends Remote {
  long getPerfectTime() throws RemoteException;
} ///:~

It looks like any other interface except that it extends Remote and all of its methods throw RemoteException. Remember that an interface and all of its methods are automatically public.

Implementing the remote interface

The server must contain a class that extends UnicastRemoteObject and implements the remote interface. This class can also have additional methods, but only the methods in the remote interface will be available to the client, of course, since the client will get only a reference to the interface, not the class that implements it.

You must explicitly define the constructor for the remote object even if you’re only defining a default constructor that calls the base-class constructor. You must write it out since it must throw RemoteException.

Here’s the implementation of the remote interface PerfectTimeI:

//: c15:rmi:PerfectTime.java
// The implementation of 
// the PerfectTime remote object.
package c15.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.net.*;

public class PerfectTime 
    extends UnicastRemoteObject
    implements PerfectTimeI {
  // Implementation of the interface:
  public long getPerfectTime() 
      throws RemoteException {
    return System.currentTimeMillis();
  }
  // Must implement constructor 
  // to throw RemoteException:
  public PerfectTime() throws RemoteException {
    // super(); // Called automatically
  }
  // Registration for RMI serving:
  public static void main(String[] args) {
    System.setSecurityManager(
      new RMISecurityManager());
    try {
      PerfectTime pt = new PerfectTime();
      Naming.bind(
        "//peppy:2005/PerfectTime", pt);
      System.out.println("Ready to do time");
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

Here, main( ) handles all the details of setting up the server. When you’re serving RMI objects, at some point in your program you must:

  1. Create and install a security manager that supports RMI. The only one available for RMI as part of the Java distribution is RMISecurityManager.
  2. Create one or more instances of a remote object. Here, you can see the creation of the PerfectTime object.
  3. Register at least one of the remote objects with the RMI remote object registry for bootstrapping purposes. One remote object can have methods that produce references to other remote objects. This allows you to set it up so the client must go to the registry only once, to get the first remote object.

Setting up the registry

Here, you see a call to the static method Naming.bind( ). However, this call requires that the registry be running as a separate process on the computer. The name of the registry server is rmiregistry, and under 32-bit Windows you say:

start rmiregistry

to start it in the background. On Unix, it is:

rmiregistry &

Like many network programs, the rmiregistry is located at the IP address of whatever machine started it up, but it must also be listening at a port. If you invoke the rmiregistry as above, with no argument, the registry’s port will default to 1099. If you want it to be at some other port, you add an argument on the command line to specify the port. For this example, the port will be located at 2005, so the rmiregistry should be started like this under 32-bit Windows:

start rmiregistry 2005

or for Unix:

rmiregistry 2005 &

The information about the port must also be given to the bind( ) command, as well as the IP address of the machine where the registry is located. But this brings up what can be a frustrating problem if you’re expecting to test RMI programs locally the way the network programs have been tested so far in this chapter. In the JDK 1.1.1 release, there are a couple of problems:[69]


  1. localhost does not work with RMI. Thus, to experiment with RMI on a single machine, you must provide the name of the machine. To find out the name of your machine under 32-bit Windows, go to the control panel and select “Network.” Select the “Identification” tab, and you’ll see your computer name. In my case, I called my computer “Peppy.” It appears that capitalization is ignored.
  2. RMI will not work unless your computer has an active TCP/IP connection, even if all your components are just talking to each other on the local machine. This means that you must connect to your Internet service provider before trying to run the program or you’ll get some obscure exception messages.

With all this in mind, the bind( ) command becomes:

Naming.bind("//peppy:2005/PerfectTime", pt);

If you are using the default port 1099, you don’t need to specify a port, so you could say:

Naming.bind("//peppy/PerfectTime", pt);

You should be able to perform local testing by leaving off the IP address and using only the identifier:

Naming.bind("PerfectTime", pt);

The name for the service is arbitrary; it happens to be PerfectTime here, just like the name of the class, but you could call it anything you want. The important thing is that it’s a unique name in the registry that the client knows to look for to procure the remote object. If the name is already in the registry, you’ll get an AlreadyBoundException. To prevent this, you can always use rebind( ) instead of bind( ), since rebind( ) either adds a new entry or replaces the one that’s already there.

Even though main( ) exits, your object has been created and registered so it’s kept alive by the registry, waiting for a client to come along and request it. As long as the rmiregistry is running and you don’t call Naming.unbind( ) on your name, the object will be there. For this reason, when you’re developing your code you need to shut down the rmiregistry and restart it when you compile a new version of your remote object.

You aren’t forced to start up rmiregistry as an external process. If you know that your application is the only one that’s going to use the registry, you can start it up inside your program with the line:

LocateRegistry.createRegistry(2005);

Like before, 2005 is the port number we happen to be using in this example. This is the equivalent of running rmiregistry 2005 from a command line, but it can often be more convenient when you’re developing RMI code since it eliminates the extra steps of starting and stopping the registry. Once you’ve executed this code, you can bind( ) using Naming as before.

Creating stubs and skeletons

If you compile and run PerfectTime.java, it won’t work even if you have the rmiregistry running correctly. That’s because the framework for RMI isn’t all there yet. You must first create the stubs and skeletons that provide the network connection operations and allow you to pretend that the remote object is just another local object on your machine.

What’s going on behind the scenes is complex. Any objects that you pass into or return from a remote object must implement Serializable (if you want to pass remote references instead of the entire objects, the object arguments can implement Remote), so you can imagine that the stubs and skeletons are automatically performing serialization and deserialization as they “marshal” all of the arguments across the network and return the result. Fortunately, you don’t have to know any of this, but you do have to create the stubs and skeletons. This is a simple process: you invoke the rmic tool on your compiled code, and it creates the necessary files. So the only requirement is that another step be added to your compilation process.

The rmic tool is particular about packages and classpaths, however. PerfectTime.java is in the package c15.rmi, and even if you invoke rmic in the same directory in which PerfectTime.class is located, rmic won’t find the file, since it searches the classpath. So you must specify the location off the class path, like so:

rmic c15.rmi.PerfectTime

You don’t have to be in the directory containing PerfectTime.class when you execute this command, but the results will be placed in the current directory.

When rmic runs successfully, you’ll have two new classes in the directory:

PerfectTime_Stub.class
PerfectTime_Skel.class

corresponding to the stub and skeleton. Now you’re ready to get the server and client to talk to each other.

Using the remote object

The whole point of RMI is to make the use of remote objects simple. The only extra thing that you must do in your client program is to look up and fetch the remote interface from the server. From then on, it’s just regular Java programming: sending messages to objects. Here’s the program that uses PerfectTime:

//: c15:rmi:DisplayPerfectTime.java
// Uses remote object PerfectTime.
package c15.rmi;
import java.rmi.*;
import java.rmi.registry.*;

public class DisplayPerfectTime {
  public static void main(String[] args) {
    System.setSecurityManager(
      new RMISecurityManager());
    try {
      PerfectTimeI t = 
        (PerfectTimeI)Naming.lookup(
          "//peppy:2005/PerfectTime");
      for(int i = 0; i < 10; i++)
        System.out.println("Perfect time = " +
          t.getPerfectTime());
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
} ///:~

The ID string is the same as the one used to register the object with Naming, and the first part represents the URL and port number. Since you’re using a URL, you can also specify a machine on the Internet.

What comes back from Naming.lookup( ) must be cast to the remote interface, not to the class. If you use the class instead, you’ll get an exception.

You can see in the method call

t.getPerfectTime( )

that once you have a reference to the remote object, programming with it is indistinguishable from programming with a local object (with one difference: remote methods throw RemoteException).

Introduction to CORBA

In large, distributed applications, your needs might not be satisfied by the preceding approaches. For example, you might want to interface with legacy datastores, or you might need services from a server object regardless of its physical location. These situations require some form of Remote Procedure Call (RPC), and possibly language independence. This is where CORBA can help.

CORBA is not a language feature; it’s an integration technology. It’s a specification that vendors can follow to implement CORBA-compliant integration products. CORBA is part of the OMG’s effort to define a standard framework for distributed, language-independent object interoperability.

CORBA supplies the ability to make remote procedure calls into Java objects and non-Java objects, and to interface with legacy systems in a location-transparent way. Java adds networking support and a nice object-oriented language for building graphical and non-graphical applications. The Java and OMG object model map nicely to each other; for example, both Java and CORBA implement the interface concept and a reference object model.

CORBA Fundamentals

The object interoperability specification developed by the OMG is commonly referred to as the Object Management Architecture (OMA). The OMA defines two components: the Core Object Model and the OMA Reference Architecture. The Core Object Model states the basic concepts of object, interface, operation, and so on. (CORBA is a refinement of the Core Object Model.) The OMA Reference Architecture defines an underlying infrastructure of services and mechanisms that allow objects to interoperate. The OMA Reference Architecture includes the Object Request Broker (ORB), Object Services (also known as CORBAservices), and common facilities.

The ORB is the communication bus by which objects can request services from other objects, regardless of their physical location. This means that what looks like a method call in the client code is actually a complex operation. First, a connection with the server object must exist, and to create a connection the ORB must know where the server implementation code resides. Once the connection is established, the method arguments must be marshaled, i.e. converted in a binary stream to be sent across a network. Other information that must be sent are the server machine name, the server process, and the identity of the server object inside that process. Finally, this information is sent through a low-level wire protocol, the information is decoded on the server side, and the call is executed. The ORB hides all of this complexity from the programmer and makes the operation almost as simple as calling a method on local object.

There is no specification for how an ORB Core should be implemented, but to provide a basic compatibility among different vendors’ ORBs, the OMG defines a set of services that are accessible through standard interfaces.

CORBA Interface Definition Language (IDL)

CORBA is designed for language transparency: a client object can call methods on a server object of different class, regardless of the language they are implemented with. Of course, the client object must know the names and signatures of methods that the server object exposes. This is where IDL comes in. The CORBA IDL is a language-neutral way to specify data types, attributes, operations, interfaces, and more. The IDL syntax is similar to the C++ or Java syntax. The following table shows the correspondence between some of the concepts common to three languages that can be specified through CORBA IDL:

CORBA IDL

Java

C++

Module

Package

Namespace

Interface

Interface

Pure abstract class

Method

Method

Member function

The inheritance concept is supported as well, using the colon operator as in C++. The programmer writes an IDL description of the attributes, methods, and interfaces that will be implemented and used by the server and clients. The IDL is then compiled by a vendor-provided IDL/Java compiler, which reads the IDL source and generates Java code.

The IDL compiler is an extremely useful tool: it doesn’t just generate a Java source equivalent of the IDL, it also generates the code that will be used to marshal method arguments and to make remote calls. This code, called the stub and skeleton code, is organized in multiple Java source files and is usually part of the same Java package.

The naming service

The naming service is one of the fundamental CORBA services. A CORBA object is accessed through a reference, a piece of information that’s not meaningful for the human reader. But references can be assigned programmer-defined, string names. This operation is known as stringifying the reference, and one of the OMA components, the Naming Service, is devoted to performing string-to-object and object-to-string conversion and mapping. Since the Naming Service acts as a telephone directory that both servers and clients can consult and manipulate, it runs as a separate process. Creating an object-to-string mapping is called binding an object, and removing the mapping is called unbinding. Getting an object reference passing a string is called resolving the name.

For example, on startup, a server application could create a server object, bind the object into the name service, and then wait for clients to make requests. A client first obtains a server object reference, resolving the string name, and then can make calls into the server using the reference.

Again, the Naming Service specification is part of CORBA, but the application that implements it is provided by the ORB vendor. The way you get access to the Naming Service functionality can vary from vendor to vendor.

An example

The code shown here will not be elaborate because different ORBs have different ways to access CORBA services, so examples are vendor specific. (The example below uses JavaIDL, a free product from Sun that comes with a light-weight ORB, a naming service, and an IDL-to-Java compiler.) In addition, since Java is young and still evolving, not all CORBA features are present in the various Java/CORBA products.

We want to implement a server, running on some machine, that can be queried for the exact time. We also want to implement a client that asks for the exact time. In this case we’ll be implementing both programs in Java, but we could also use two different languages (which often happens in real situations).

Writing the IDL source

The first step is to write an IDL description of the services provided. This is usually done by the server programmer, who is then free to implement the server in any language in which a CORBA IDL compiler exists. The IDL file is distributed to the client side programmer and becomes the bridge between languages.

The example below shows the IDL description of our ExactTime server:

//: c15:corba:ExactTime.idl
//# You must install idltojava.exe from 
//# java.sun.com and adjust the settings to use
//# your local C preprocessor in order to compile
//# This file. See docs at java.sun.com.
module remotetime {
   interface ExactTime {
      string getTime();
   };
}; ///:~

This is a declaration of the ExactTime interface inside the remotetime namespace. The interface is made up of one single method that gives back the current time in string format.

Creating stubs and skeletons

The second step is to compile the IDL to create the Java stub and skeleton code that we’ll use for implementing the client and the server. The tool that comes with the JavaIDL product is idltojava:

idltojava remotetime.idl

This will automatically generate code for both the stub and the skeleton. Idltojava generates a Java package named after the IDL module, remotetime, and the generated Java files are put in the remotetime subdirectory. _ExactTimeImplBase.java is the skeleton that we’ll use to implement the server object, and _ExactTimeStub.java will be used for the client. There are Java representations of the IDL interface in ExactTime.java and a couple of other support files used, for example, to facilitate access to the naming service operations.

Implementing the server and the client

Below you can see the code for the server side. The server object implementation is in the ExactTimeServer class. The RemoteTimeServer is the application that creates a server object, registers it with the ORB, gives a name to the object reference, and then sits quietly waiting for client requests.

//: c15:corba:RemoteTimeServer.java
import remotetime.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import java.util.*;
import java.text.*;

// Server object implementation
class ExactTimeServer extends _ExactTimeImplBase {
  public String getTime(){
    return DateFormat.
        getTimeInstance(DateFormat.FULL).
          format(new Date(
              System.currentTimeMillis()));
  }
}

// Remote application implementation
public class RemoteTimeServer {
  public static void main(String[] args) {
    try {
      // ORB creation and initialization:
      ORB orb = ORB.init(args, null);
      // Create the server object and register it:
      ExactTimeServer timeServerObjRef = 
        new ExactTimeServer();
      orb.connect(timeServerObjRef);
      // Get the root naming context:
      org.omg.CORBA.Object objRef = 
        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef = 
        NamingContextHelper.narrow(objRef);
      // Assign a string name to the 
      // object reference (binding):
      NameComponent nc = 
        new NameComponent("ExactTime", "");
      NameComponent[] path = { nc };
      ncRef.rebind(path, timeServerObjRef);
      // Wait for client requests:
      java.lang.Object sync =
        new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    }
    catch (Exception e) {
      System.out.println(
         "Remote Time server error: " + e);
      e.printStackTrace(System.out);
    }
  }
} ///:~

As you can see, implementing the server object is simple; it’s a regular Java class that inherits from the skeleton code generated by the IDL compiler. Things get a bit more complicated when it comes to interacting with the ORB and other CORBA services.

Some CORBA services

This is a short description of what the JavaIDL-related code is doing (primarily ignoring the part of the CORBA code that is vendor dependent). The first line in main( ) starts up the ORB, and of course, this is because our server object will need to interact with it. Right after the ORB initialization, a server object is created. Actually, the right term would be a transient servant object: an object that receives requests from clients, and whose lifetime is the same as the process that creates it. Once the transient servant object is created, it is registered with the ORB, which means that the ORB knows of its existence and can now forward requests to it.

Up to this point, all we have is timeServerObjRef, an object reference that is known only inside the current server process. The next step will be to assign a stringified name to this servant object; clients will use that name to locate the servant object. We accomplish this operation using the Naming Service. First, we need an object reference to the Naming Service; the call to resolve_initial_references( ) takes the stringified object reference of the Naming Service that is “NameService,” in JavaIDL, and returns an object reference. This is cast to a specific NamingContext reference using the narrow( ) method. We can use now the naming services.

To bind the servant object with a stringified object reference, we first create a NameComponent object, initialized with “ExactTime,” the name string we want to bind to the servant object. Then, using the rebind( ) method, the stringified reference is bound to the object reference. We use rebind( ) to assign a reference, even if it already exists, whereas bind( ) raises an exception if the reference already exists. A name is made up in CORBA by a sequence of NameContexts—that’s why we use an array to bind the name to the object reference.

The servant object is finally ready for use by clients. At this point, the server process enters a wait state. Again, this is because it is a transient servant, so its lifetime is confined to the server process. JavaIDL does not currently support persistent objects—objects that survive the execution of the process that creates them.

Now that we have an idea of what the server code is doing, let’s look at the client code:

//: c15:corba:RemoteTimeClient.java
import remotetime.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

public class RemoteTimeClient {
  public static void main(String[] args) {
    try {
      // ORB creation and initialization:
      ORB orb = ORB.init(args, null);
      // Get the root naming context:
      org.omg.CORBA.Object objRef = 
        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef = 
        NamingContextHelper.narrow(objRef);
      // Get (resolve) the stringified object 
      // reference for the time server:
      NameComponent nc = 
        new NameComponent("ExactTime", "");
      NameComponent[] path = { nc };
      ExactTime timeObjRef = 
        ExactTimeHelper.narrow(
          ncRef.resolve(path));
      // Make requests to the server object:
      String exactTime = timeObjRef.getTime();
      System.out.println(exactTime);
    } catch (Exception e) {
      System.out.println(
         "Remote Time server error: " + e);
      e.printStackTrace(System.out);
    }
  }
} ///:~

The first few lines do the same as they do in the server process: the ORB is initialized and a reference to the naming service server is resolved. Next, we need an object reference for the servant object, so we pass the stringified object reference to the resolve( ) method, and we cast the result into an ExactTime interface reference using the narrow( ) method. Finally, we call getTime( ).

Activating the name service process

Finally we have a server and a client application ready to interoperate. You’ve seen that both need the naming service to bind and resolve stringified object references. You must start the naming service process before running either the server or the client. In JavaIDL, the naming service is a Java application that comes with the product package, but it can be different with other products. The JavaIDL naming service runs inside an instance of the JVM and listens by default to network port 900.

Activating the server and the client

Now you are ready to start your server and client application (in this order, since our server is transient). If everything is set up correctly, what you’ll get is a single output line on the client console window, giving you the current time. Of course, this might be not very exciting by itself, but you should take one thing into account: even if they are on the same physical machine, the client and the server application are running inside different virtual machines and they can communicate via an underlying integration layer, the ORB and the Naming Service.

This is a simple example, designed to work without a network, but an ORB is usually configured for location transparency. When the server and the client are on different machines, the ORB can resolve remote stringified references using a component known as the Implementation Repository. Although the Implementation Repository is part of CORBA, there is almost no specification, so it differs from vendor to vendor.

As you can see, there is much more to CORBA than what has been covered here, but you should get the basic idea. If you want more information about CORBA, the place to start is the OMG Web site, at http://www.omg.org. There you’ll find documentation, white papers, proceedings, and references to other CORBA sources and products.

Java Applets and CORBA

Java applets can act as CORBA clients. This way, an applet can access remote information and services exposed as CORBA objects. But an applet can connect only with the server from which it was downloaded, so all the CORBA objects the applet interacts with must be on that server. This is the opposite of what CORBA tries to do: give you complete location transparency.

This is an issue of network security. If you’re on an intranet, one solution is to loosen the security restrictions on the browser. Or, set up a firewall policy for connecting with external servers.

Some Java ORB products offer proprietary solutions to this problem. For example, some implement what is called HTTP Tunneling, while others have their special firewall features.

This is too complex a topic to be covered in an appendix, but it is definitely something you should be aware of.

CORBA vs. RMI

You saw that one of the main CORBA features is RPC support, which allows your local objects to call methods in remote objects. Of course, there already is a native Java feature that does exactly the same thing: RMI (see Chapter 15). While RMI makes RPC possible between Java objects, CORBA makes RPC possible between objects implemented in any language. It’s a big difference.

However, RMI can be used to call services on remote, non-Java code. All you need is some kind of wrapper Java object around the non-Java code on the server side. The wrapper object connects externally to Java clients via RMI, and internally connects to the non-Java code using one of the techniques shown above, such as JNI or J/Direct.

This approach requires you to write a kind of integration layer, which is exactly what CORBA does for you, but then you don’t need a third-party ORB.

Enterprise Java Beans

[70] By now, you've been introduced to CORBA and RMI. But could you imagine trying to develop a large-scale application using CORBA and/or RMI? Your manager has asked you to develop a multi-tiered application to view and update records in a database through a Web interface. You sit back and think of what that really means. Sure, you can write a database application using JDBC, a Web interface using JSP/Servlets, and a distributed system using CORBA/RMI. But what extra considerations must you make when developing a distributed object based system rather than just knowing API's? Here are the issues:

Performance: The new distributed objects that you are creating are going to have to perform, as they potentially could service many clients at a time. So you'll want to think of optimization techniques such as caching, pooling of resources (e.g., JDBC database connections). You'll also have to manage the lifecycle of your distributed object.

Scalability: The distributed objects must also be scalable. Scalable in a distributed application sense means that the number of instances of your distributed object can be increased and moved over to a different machine without the modification of any code. Take for example a system that you develop internally as a small lookup of clients inside your organization from a database. The application works well when you use it, but your manager has seen it and has said "Robert, that is an excellent system, get it on our public Web-site now!!!"-Will my distributed object be able to handle the load of a potentially limitless demand?

Security: Does my distributed object manage the authorization of the clients that accesses it? Can I add new users and roles to it without recompilation?

Distributed Transactions: Can my distributed object reference distributed-transactions transparently? Can I update my Oracle and Sybase databases simultaneously within the same transaction and roll them both back if a certain criteria is not met?

Reusability: Have I created my distributed object so that I can move it into another vendors' application server? Can I resell my distributed object (component) to somebody else? Can I buy somebody else's component and use it without having to recompile and 'hack it into shape'?

Availability: If one of the machines in my system was to go down, are my clients able to automatically fail-over to back up copies of my objects running on other machines?

As you can see from the above, the considerations that a developer must make when developing a distributed system and we haven't even mentioned solving the problem that we were originally trying to solve!

So you now have your list of extra problems that you must solve. So how do you go about doing it? Surely somebody must have done this before? Could I use some well-known design patterns to help me solve these problems? Then an idea flashes in your head... "I could create a framework that handles all of these issues and write my components on top of the framework!".... This is where Enterprise JavaBeans comes into the picture.

Sun, along with other leading distributed object vendors (listed at URL HERE!) realized that sooner or later every development team would be reinventing the wheel. So they created the Enterprise JavaBeans specification (EJB). EJB is a specification for a server-side component model that tackles all of the considerations mentioned above using a defined, standard approach that allows developers to create components-which are actually called Enterprise JavaBeans (EJB's)-that are isolated from low-level 'plumbing' code and focus solely on providing business logic. Because EJB's are defined as a standard they can be used without being vendor dependent.

What’s defined in the EJB specification?

The Enterprise JavaBeans specification, currently at version 1.1 - public release 2 - defines a server side component model. It defines 6 roles that are used to perform the tasks in development and deployment as well as defining the components of the system.

Roles

The EJB specification defines roles that are used during in the development, deployment and running of a distributed system. Vendors, administrators and developers play the various roles. They allow the partitioning of technical and domain knowledge. This allows the vendor to provide a technically sound framework and the developers to create domain specific components e.g., an Accounting component. The same party can perform one or many roles. The roles defined in the EJB specification have been summarized in the following table:

Role

Responsibility

Enterprise Bean Provider

The developer who is responsible for creating reusable EJB components. These components are packaged into a special jar file (ejb-jar file).

Application Assembler

Create and assemble applications from a collection of ejb-jar files. This includes writing applications that utilize the collection of EJB’s (e.g., Servlets, JSP, Swing etc. etc.)

Deployer

The Deployer’s role of the to take the collection of ejb-jar files from the Assembler and/or Bean Provider and deploy them into a run-time environment - one or many EJB Container(s).

EJB Container/Server Provider

Provide a run-time environment and tools that are used to deploy, administer and “run” EJB components.

System Administrator

Over see the most important goal of the entire system - That it is up and running. Management of a distributed application can consist of many different components and services all configured and interacting together correctly.


Components of EJB

EJB components are reusable business logic. EJB components adhere to strict standards and design patterns as defined in the EJB specification. This allows the components to be portable and also allow other services—such as security, caching and distributed transactions - to be performed on the components’ behalf. An Enterprise Bean Provider is responsible for developing EJB components. The internals of an EJB component are covered in - “What makes up an EJB component?”

EJB Container

The EJB Container is a run-time environment that contains -or runs—EJB components and provides a set of standard services to the components. The EJB Containers responsibilities are tightly defined by the specification to allow for vendor neutrality. The EJB container provides the low-level “plumbing” of EJB, including distributed transactions, security, life cycle management of beans, caching, threading and session management. The EJB Container Provider is responsible for providing an EJB Container.

EJB Server

An EJB Server is defined as an Application Server that contains and runs 1 or more EJB Containers. that both the Container and Server are the same vendor. The EJB Server Provider is responsible for providing an EJB Server. The specification suggests and you can assume for this introduction, that the EJB Container and EJB Server are the same.

Java Naming and Directory Interface (JNDI)

Java Naming and Directory Interface (JNDI) is used in Enterprise JavaBeans as the naming service for EJB Components on the network and other container services such as transactions. JNDI maps very closely to other naming and directory standards such as CORBA CosNaming and can actually be implemeted as a wrapper on top of it.

Java Transaction API / Java Transaction Service (JTA/JTS)

JTA/JTS is used in Enterprise JavaBeans as the transactional API. An Enterprise Bean Provider can use the JTS to create transactional code although the EJB Container commonly implements transactions in EJB on the EJB components’ behalf. The Deployer can define the transactional attributes of an EJB component at deployment time. The EJB Container is responsible for handling the transaction whether it is local or distributed. The JTS specification is the Java mapping to the CORBA OTS (Object Transaction Service)

CORBA and RMI/IIOP

The EJB specification defines interoperablility with CORBA. The 1.1 specification quotes “The Enterprise JavaBeans architecture will be compatible with the CORBA protocols.” CORBA interoperability is achieved through the mapping of EJB services such as JTS and JNDI to corresponding CORBA services and the implementation of RMI on top of the CORBA protocol IIOP.

Use of CORBA and RMI/IIOP in Enterprise JavaBeans is implemented in the EJB Container and is the responsibility of the EJB Container provider. Use of CORBA and RMI/IIOP in the EJB Container is hidden from the EJB Component itself. This means that the Enterprise Bean Provider can write their EJB Component and deploy it into any EJB Container without any regard of which communication protocol is being used.

The components and available services of EJB.



What makes up an EJB component?


Enterprise Bean

The Enterprise Bean is a Java class that the Enterprise Bean Provider develops. It implements an EnterpriseBean interface (more detail in a later section) and provides the implementation of the business methods that the component is to perform. The class does not implement any authorization/authentication code, multithreading, transactional code.

Home Interface

Every Enterprise Bean that is created must have an associated Home interface. The Home interface is used as a factory for your EJB. Clients use the Home interface to find an instance of your EJB or create a new instance of your EJB.

Remote Interface

The Remote interface is a Java Interface that reflects the methods of your Enterprise Bean that you wish to expose to the outside world. The Remote interface plays a similar role to a CORBA IDL interface.

Deployment Descriptor

The Deployment Descriptor is an XML file that contains information about your EJB. Using XML allows the Deployer to easily change attributes about your EJB. The configurable attributes defined in the Deployment Descriptor include:

EJB-Jar File

The EJB-Jar file is a normal java jar file that contains your EJB, Home and Remote interfaces, as well as the Deployment Descriptor.



How does EJB work?

Now that we have our EJB-Jar file containing our Bean, Home and Remote interfaces and Deployment Descriptor, Let’s take a look at how all of these pieces fit together and why Home and Remote interfaces are needed and how the EJB Container uses them.

Who implements the Home and Remote Interfaces?

The EJB Container implements the Home and Remote interfaces that are in our EJB-Jar file. Because the EJB Container implements the Home interface that - as mentioned earlier - provides methods to create and find your EJB. This means that the EJB Container is responsible for the lifecycle management of your EJB. This level of indirection allows for optimizations to occur. For example 5 clients simultaneously request to create an EJB through a Home Interface, the EJB Container could create only one and share that EJB between all 5 clients. This is achieved through the Remote Interface, which is again implemented by the EJB Container. The implemented Remote object plays the role of a proxy object to the EJB.

The following diagram show the level of indirection achieved by this approach and that all calls to the EJB are ‘proxied’ through the EJB Container via the Home and Remote interfaces. This level of indirection is also the reason why the EJB Container can control security and transactional behavior.


Types of EJB’s

There should be one question in your head from the previous section, “Surely sharing the same EJB between clients can improve performance, but what If I want to maintain state on my server?”

The Enterprise JavaBeans specification defines different types of EJB’s that have different characteristics and exhibit different behavior. Two categories of EJB’s have been defined in the specification. Session Beans and Entity Beans, and each of these categories has variations. A hierarchy of the various types of EJB components is shown in the following figure.


Session Beans

Session Beans are used to represent Use-Cases or Workflow on behalf of a client. They represent operations on persistent data, not persistent data itself. There are two types of Session Beans, Stateless and Stateful. All Session Beans must implement the javax.ejb.SessionBean interface. The EJB Container governs the life of a Session Bean. If the EJB Container crashes, data for all Stateful Session Beans could be lost. Some high-end EJB Containers provide recovery for Stateful Session Beans.

Stateless Session Beans

Stateless Session Beans are the simplest type of EJB component to implement. They do not maintain any conversational state with clients between method invocations so they are easily reusable on the server side and because they can be cached, they scale well on demand. When using Stateless Session Beans, all state must be stored outside of the EJB.

Stateful Session Beans

Stateful Session Beans - as you could probably guess - maintain state between invocations. They have a 1 to 1 logical mapping to a client and can maintain state within themselves. The EJB Container is responsible for pooling and caching of Stateful Session Beans, which is achieved through Passivation and Activation.

Entity Beans

Entity Beans are components that represent persistent data and behavior of this data. Entity Beans can be shared amongst multiple clients, the same as data in a database. The EJB Container is responsible for caching Entity Beans and for maintaining the integrity of the Entity Beans. The life of an Entity Bean outlives the EJB Container, so if an EJB Container crashes, the Entity Bean is expected to still be available when the EJB Container becomes available.

There are two types of Entity Beans, those that have Bean-Managed persistence and Container Managed persistence.

Container Managed Persistence (CMP)

A CMP Entity Bean has its’ persistence implemented on its behalf by the EJB Container. Through attributes specified in the Deployment Descriptor, the EJB Container will map the Entity Bean’s attributes to some persistent store (usually -but not always—a database). CMP reduces the time to develop and dramatically reduces the amount of code required for the EJB.

Bean Managed Persistence (BMP)

A BMP Entity Bean has its’ persistence implemented by the Enterprise Bean Provider. The Enterprise Bean Provider is responsible for implementing the logic required to create a new EJB, update some attributes of the EJBS, delete an EJB and find an EJB from persistent store. This usually involves writing JDBC code to interact with a database or other persistent store. With BMP, the developer is in full control of how the Entity Bean persistence is managed.

BMP also gives flexibility where a CMP implementation may not be available e.g., if you wanted to create an EJB that wrapped some code on an existing mainframe system, you could write your persistence using CORBA.

How do I put the ‘E’ in my existing JavaBeans?

There is much confusion about the relationship between the JavaBeans component model and the Enterprise JavaBeans specification. Whilst both the JavaBeans and Enterprise JavaBeans specifications share the same objectives in promoting reuse and portability of Java code between development and deployment tools with the use of standard design patterns, the motives behind each specification are geared to solve different problems.

The standards defined in the JavaBeans component model are designed for creating reusable components that are typically used in IDE development tools and are commonly, although not exclusively visual components.

The Enterprise JavaBeans specification defines a component model for developing server side java code. Because EJB’s can potentially run on many different server-side platforms -including mainframes that do not have visual displays - An EJB cannot make use of the java.awt package.

Developing an Enterprise Java Bean

We will now implement the “Perfect Time” example from the previous RMI section as an Enterprise JavaBean component. The example will be a simple Stateless Session Bean. Enterprise JavaBean components will consist of at least one class and two interfaces.

The first interface defined is the Remote Interface to our Enterprise JavaBean component. When you create a Remote interface for an EJB , you must follow these guidelines:

  1. The remote interface must be public.
  2. The remote interface must extend the interface javax.ejb.EJBObject.
  3. Each method in the remote interface must declare java.rmi.RemoteException in its throws clause in addition to any application-specific exceptions.
  4. Any object passed as an argument or return value (either directly or embedded within a local object) must be a valid RMI-IIOP data type (this includes other EJB objects)

Here is the simple remote interface for our PerfectTime EJB:

//: c15:ejb:PerfectTime.java
//# You must install the J2EE Java Enterprise 
//# Edition from java.sun.com and add j2ee.jar
//# To your CLASSPATH in order to compile
//# This file. See details at java.sun.com.
// Remote Interface of PerfectTimeBean
import java.rmi.*;
import javax.ejb.*;

public interface PerfectTime extends EJBObject {
  public long getPerfectTime() 
    throws RemoteException;
} ///:~

The second interface defined is the Home Interface to our Enterprise JavaBean component. The Home interface is the factory where our component will be created. The Home interface can define create or finder methods. Create methods create instances of EJB’s, finder methods locate existing EJB’s and are used for Entity Beans only. When you create a Home interface for an EJB , you must follow these guidelines:

  1. The Home interface must be public.
  2. The Home interface must extend the interface javax.ejb.EJBHome.
  3. Each method in the Home interface must declare java.rmi.RemoteException in its throws clause as well as a javax.ejb.CreateException
  4. The return value of a create method must be a Remote Interface
  5. The return value of a finder method (Entity Beans only) must be a Remote Interface or java.util.Enumeration or java.util.Collection
  6. Any object passed as an argument (either directly or embedded within a local object) must be a valid RMI-IIOP data type (this includes other EJB objects)

The standard naming convention for Home interfaces is to take the Remote interface name and append “Home” to the end. Following is the Home interface for our PerfectTime EJB:

//: c15:ejb:PerfectTimeHome.java
// Home Interface of PerfectTimeBean.
import java.rmi.*;
import javax.ejb.*;

public interface PerfectTimeHome extends EJBHome {
  public PerfectTime create() 
    throws CreateException, RemoteException;
} ///:~

Now that we have defined the interfaces of our component, we can now implement the business logic behind it. When you create your EJB implementation class, you must follow these guidelines, (note that you should consult the EJB specification for a complete list of guidelines when developing Enterprise JavaBeans):

  1. The class must be public.
  2. The class must implement an EJB interface (either javax.ejb.SessionBean or javax.ejb.EntityBean).
  3. The class should define methods that map directly to the methods in the Remote interface, Note that the class does not implement the Remote interface, it mirrors the methods in the Remote interface but does NOT throw java.rmi.RemoteException.
  4. Define one or more ejbCreate( ) methods to initialize your EJB.
  5. The return value and arguments of all methods must be valid RMI-IIOP data types.
//: c15:ejb:PerfectTimeBean.java
// Simple Stateless Session Bean 
// that returns current system time.
import java.rmi.*;
import javax.ejb.*;

public class PerfectTimeBean 
  implements SessionBean {
  private SessionContext sessionContext;
  //return current time
  public long getPerfectTime() { 
     return System.currentTimeMillis();
  }
  // EJB methods
  public void 
  ejbCreate() throws CreateException {}
  public void ejbRemove() {}
  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void 
  setSessionContext(SessionContext ctx) {
    sessionContext = ctx;
  }
}///:~

Notice that the EJB methods (ejbCreate( ), ejbRemove( ), ejbActivate( ), ejbPassivate( ) ) are all empty. These methods are invoked by the EJB Container and are used to control the state of our component. As this is a simple example, we can leave these empty. The setSessionContext( ) method passes a javax.ejb.SessionContext object which contains information about context that the component is in, such as the current transaction and security information.

After we have created our Enterprise Java Bean, we then need to create a Deployment Descriptor. In EJB 1.1, the Deployment Descriptor is an XML file that describes the EJB component. The Deployment Descriptor should be stored in a file called ejb-jar.xml.

<?xml version="1.0" encoding="Cp1252"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>
  <description>example for chapter 15</description>
  <display-name></display-name>
  <small-icon></small-icon>
  <large-icon></large-icon>
  <enterprise-beans>
    <session>
      <ejb-name>PerfectTime</ejb-name>
      <home>PerfectTimeHome</home>
      <remote>PerfectTime</remote>
      <ejb-class>PerfectTimeBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
    </session>
  </enterprise-beans>
  <ejb-client-jar></ejb-client-jar>
</ejb-jar>

Inside the <session> tag of our deployment descriptor we can see that our Component, the Remote interface and Home interfaces are begin defined. Deployment Descriptors can easily be automatically generated inside tools such as JBuilder.

Along with the standard ejb-jar.xml deployment descriptor the EJB 1.1 specification states that any vendor specific tags should be stored in a separate file, this is to achieve high portability between components and different brands EJB containers.

Now that we have created our component, and defined it’s composition in the Deployment Descriptor we then need to archive the files inside a standard Java Archive (JAR) file. The Deployment Descriptors should be placed inside the /META-INF sub-directory of the Jar file.

Once we have defined our EJB component in the Deployment Descriptor, the Deployer should then deploy the EJB component into the EJB Container. At the time of writing, this process was quite "gui intensive" and specific to each individual EJB Container, so we decided not to document the entire deployment process in this overview. Every EJB Container, however will have a documented process for deploying an EJB.

Because an EJB component is a distributed object, the deployment process should also create some client stubs for calling the EJB component. These classes whould be placed on the classpath of the client application. Because EJB components can be implemented on top of RMI-IIOP (CORBA) or RMI-JRMP, the stubs generated could vary between EJB Containers, nevertheless they are generated classes.

When a client program wishes to invoke an EJB, it must lookup the EJB component inside JNDI and obtain a reference to the Home interface of the EJB component. The Home Interface can then be invoked to create an instance of the EJB, which can then be invoked.

In this example the Client program is a simple Java program, but you should remember that it could just as easily be a Servlet, a JSP even a CORBA or RMI distributed object.

The PerfectTimeClient code is as follows.

//: c15:ejb:PerfectTimeClient.java
// Client program for PerfectTimeBean

public class PerfectTimeClient {
public static void 
main(String[] args) throws Exception {
  // Get a JNDI context using the 
  // JNDI Naming service:
  javax.naming.Context context = 
    new javax.naming.InitialContext();
  // Look up the home interface in the 
  // JNDI Naming service:
  Object ref = context.lookup("perfectTime");
  // Cast the remote object to the home interface:
  PerfectTimeHome home = (PerfectTimeHome)
    javax.rmi.PortableRemoteObject.narrow(
      ref, PerfectTimeHome.class);
  // Create a remote object from the home interface:
  PerfectTime pt = home.create();
  // Invoke  getPerfectTime()
  System.out.println(
    "Perfect Time EJB invoked, time is: " + 
    pt.getPerfectTime() );
  }
} ///:~


In Summary

The Enterprise JavaBeans specification - although intially seems very daunting - is a dramatic step forward in the standardization and simplification or distributed object computing. It is a major piece of the Java 2, Enterprise Edition Platform and is receiving much support from the Distributed Object community. Many tools are currently available or will be in the near future to help accelerate the development of EJB components.

This overview was aimed at giving you a brief tour as to what EJB is all about. For more information about the Enterprise JavaBeans specification you should see the Official Enterprise JavaBeans Home Page at http://java.sun.com/products/ejb/. Here you can download the latest specification as well as the Java 2, Enterprise Edition Reference Implementation, which you can use to develop and deploy your own EJB components.

Jini: distributed services

This section[71] gives an overview of Sun Microsystems's Jini technology. It describes some Jini nuts and bolts and shows how Jini's architecture helps to raise the level of abstraction in distributed systems programming, effectively turning network programming into object-oriented programming.

Jini in context

Traditionally, operating systems have been designed with the assumption that a computer will have a processor, some memory, and a disk. When you boot a computer, the first thing it does is look for a disk. If it doesn't find a disk, it can't function as a computer. Increasingly, however, computers are appearing in a different guise: as embedded devices that have a processor, some memory, and a network connection—but no disk. The first thing a cellphone does when you boot it up, for example, is look for the telephone network. If it doesn't find the network, it can't function as a cellphone. This trend in the hardware environment, from disk-centric to network-centric, will affect how we organize our software—and that's where Jini comes in.

Jini is an attempt to rethink computer architecture, given the rising importance of the network and the proliferation of processors in devices that have no disk drive. These devices, which will come from many different vendors, will need to interact over a network. The network itself will be very dynamic—devices and services will be added and removed regularly. Jini provides mechanisms to enable smooth adding, removal, and finding of devices and services on the network. In addition, Jini provides a programming model that makes it easier for programmers to get their devices talking to each other.

Building on top of Java, object serialization, and RMI (which enable objects to move around the network from virtual machine to virtual machine) Jini attempts to extend the benefits of object-oriented programming to the network. Instead of requiring device vendors to agree on the network protocols through which their devices can interact, Jini enables the devices to talk to each other through interfaces to objects.

What is Jini?

Jini is a set of APIs and network protocols that can help you build and deploy distributed systems that are organized as federations of services. A service can be anything that sits on the network and is ready to perform a useful function. Hardware devices, software, communications channels—even human users themselves—can be services. A Jini-enabled disk drive, for example, could offer a “storage” service. A Jini-enabled printer could offer a “printing” service. A federation of services, then, is a set of services, currently available on the network, that a client (meaning a program, service, or user) can bring together to help it accomplish some goal.

To perform a task, a client enlists the help of services. For example, a client program might upload pictures from the image storage service in a digital camera, download the pictures to a persistent storage service offered by a disk drive, and send a page of thumbnail-sized versions of the images to the printing service of a color printer. In this example, the client program builds a distributed system consisting of itself, the image storage service, the persistent storage service, and the color-printing service. The client and services of this distributed system work together to perform the task: to offload and store images from a digital camera and print a page of thumbnails.

The idea behind the word federation is that the Jini view of the network doesn't involve a central controlling authority. Because no one service is in charge, the set of all services available on the network form a federation—a group composed of equal peers. Instead of a central authority, Jini's run-time infrastructure merely provides a way for clients and services to find each other (via a lookup service, which stores a directory of currently available services). After services locate each other, they are on their own. The client and its enlisted services perform their task independently of the Jini run-time infrastructure. If the Jini lookup service crashes, any distributed systems brought together via the lookup service before it crashed can continue their work. Jini even includes a network protocol that clients can use to find services in the absence of a lookup service.

How Jini works

Jini defines a run-time infrastructure that resides on the network and provides mechanisms that enable you to add, remove, locate, and access services. The run-time infrastructure resides in three places: in lookup services that sit on the network, in the service providers (such as Jini-enabled devices), and in clients. Lookup services are the central organizing mechanism for Jini-based systems. When new services become available on the network, they register themselves with a lookup service. When clients wish to locate a service to assist with some task, they consult a lookup service.

The run-time infrastructure uses one network-level protocol, called discovery, and two object-level protocols, called join and lookup. Discovery enables clients and services to locate lookup services. Join enables a service to register itself in a lookup service. Lookup enables a client to query for services that can help accomplish its goals.

The discovery process

Discovery works like this: Imagine you have a Jini-enabled disk drive that offers a persistent storage service. As soon as you connect the drive to the network, it broadcasts a presence announcement by dropping a multicast packet onto a well-known port. Included in the presence announcement is an IP address and port number where the disk drive can be contacted by a lookup service.

Lookup services monitor the well-known port for presence announcement packets. When a lookup service receives a presence announcement, it opens and inspects the packet. The packet contains information that enables the lookup service to determine whether or not it should contact the sender of the packet. If so, it contacts the sender directly by making a TCP connection to the IP address and port number extracted from the packet. Using RMI, the lookup service sends an object, called a service registrar, across the network to the originator of the packet. The purpose of the service registrar object is to facilitate further communication with the lookup service. By invoking methods on this object, the sender of the announcement packet can perform join and lookup on the lookup service. In the case of the disk drive, the lookup service would make a TCP connection to the disk drive and would send it a service registrar object, through which the disk drive would then register its persistent storage service via the join process.

The join process

Once a service provider has a service registrar object, the end product of discovery, it is ready to do a join—to become part of the federation of services that are registered in the lookup service. To do a join, the service provider invokes the register( ) method on the service registrar object, passing as a parameter an object called a service item, a bundle of objects that describe the service. The register( ) method sends a copy of the service item up to the lookup service, where the service item is stored. Once this has completed, the service provider has finished the join process: its service has become registered in the lookup service.

The service item is a container for several objects, including an object called a service object, which clients can use to interact with the service. The service item can also include any number of attributes, which can be any object. Some potential attributes are icons, classes that provide GUIs for the service, and objects that give more information about the service.

Service objects usually implement one or more interfaces through which clients interact with the service. For example, a lookup service is a Jini service, and its service object is the service registrar. The register( ) method invoked by service providers during join is declared in the ServiceRegistrar interface (a member of the net.jini.core.lookup package), which all service registrar objects implement. Clients and service providers talk to the lookup service through the service registrar object by invoking methods declared in the ServiceRegistrar interface. Likewise, a disk drive would provide a service object that implemented some well-known storage service interface. Clients would look up and interact with the disk drive by this storage service interface.

The lookup process

Once a service has registered with a lookup service via the join process, that service is available for use by clients who query that lookup service. To build a distributed system of services that will work together to perform some task, a client must locate and enlist the help of the individual services. To find a service, clients query lookup services via a process called lookup.

To perform a lookup, a client invokes the lookup( ) method on a service registrar object. (A client, like a service provider, gets a service registrar through the previously-described process of discovery.) The client passes as an argument to lookup( ) a service template, an object that serves as search criteria for the query. The service template can include a reference to an array of Class objects. These Class objects indicate to the lookup service the Java type (or types) of the service object desired by the client. The service template can also include a service ID, which uniquely identifies a service, and attributes, which must exactly match the attributes uploaded by the service provider in the service item. The service template can also contain wildcards for any of these fields. A wildcard in the service ID field, for example, will match any service ID. The lookup( ) method sends the service template to the lookup service, which performs the query and sends back zero to any matching service objects. The client gets a reference to the matching service objects as the return value of the lookup( ) method.

In the general case, a client looks up a service by Java type, usually an interface. For example, if a client needed to use a printer, it would compose a service template that included a Class object for a well-known interface to printer services. All printer services would implement this well-known interface. The lookup service would return a service object (or objects) that implemented this interface. Attributes can be included in the service template to narrow the number of matches for such a type-based search. The client would use the printer service by invoking methods from the well-known printer service interface on the service object.

Separation of interface and implementation

Jini's architecture brings object-oriented programming to the network by enabling network services to take advantage of one of the fundamentals of objects: the separation of interface and implementation. For example, a service object can grant clients access to the service in many ways. The object can actually represent the entire service, which is downloaded to the client during lookup and then executed locally. Alternatively, the service object can serve merely as a proxy to a remote server. Then when the client invokes methods on the service object, it sends the requests across the network to the server, which does the real work. A third option is for the local service object and a remote server to each do part of the work.

One important consequence of Jini's architecture is that the network protocol used to communicate between a proxy service object and a remote server does not need to be known to the client. As illustrated in the figure below, the network protocol is part of the service's implementation. This protocol is a private matter decided upon by the developer of the service. The client can communicate with the service via this private protocol because the service injects some of its own code (the service object) into the client's address space. The injected service object could communicate with the service via RMI, CORBA, DCOM, some home-brewed protocol built on top of sockets and streams, or anything else. The client simply doesn't need to care about network protocols, because it can talk to the well-known interface that the service object implements. The service object takes care of any necessary communication on the network.


The client talks to the service through a well-known interface

Different implementations of the same service interface can use completely different approaches and network protocols. A service can use specialized hardware to fulfill client requests, or it can do all its work in software. In fact, the implementation approach taken by a single service can evolve over time. The client can be sure it has a service object that understands the current implementation of the service, because the client receives the service object (by way of the lookup service) from the service provider itself. To the client, a service looks like the well-known interface, regardless of how the service is implemented.

Abstracting distributed systems

Jini attempts to raise the level of abstraction for distributed systems programming, from the network protocol level to the object interface level. In the emerging proliferation of embedded devices connected to networks, many pieces of a distributed system may come from different vendors. Jini makes it unnecessary for vendors of devices to agree on network level protocols that allow their devices to interact. Instead, vendors must agree on Java interfaces through which their devices can interact. The processes of discovery, join, and lookup, provided by the Jini run-time infrastructure, will enable devices to locate each other on the network. Once they locate each other, devices will be able to communicate with each other through Java interfaces.

JavaSpaces

Summary

There’s actually a lot more to networking than can be covered in this introductory treatment. Java networking also provides fairly extensive support for URLs, including protocol handlers for different types of content that can be discovered at an Internet site. You can find other Java networking features fully and carefully described in Java Network Programming by Elliotte Rusty Harold (O’Reilly, 1997).

Exercises

  1. Compile and run the JabberServer and JabberClient programs in this chapter. Now edit the files to remove all of the buffering for the input and output, then compile and run them again to observe the results.
  2. Create a server that asks for a password, then opens a file and sends the file over the network connection. Create a client that connects to this server, gives the appropriate password, then captures and saves the file. Test the pair of programs on your machine using the localhost (the local loopback IP address 127.0.0.1 produced by calling InetAddress.getByName(null)).
  3. Modify the server in Exercise 2 so that it uses multithreading to handle multiple clients.
  4. Modify JabberClient so that output flushing doesn’t occur and observe the effect.
  5. Modify MultiJabberServer so that it uses thread pooling. Instead of throwing away a thread each time a client disconnects, the thread should put itself into an “available pool” of threads. When a new client wants to connect, the server will look in the available pool for a thread to handle the request, and if one isn’t available, make a new one. This way the number of threads necessary will naturally grow to the required quantity. The value of thread pooling is that it doesn’t require the overhead of creating and destroying a new thread for each new client.
  6. Build on ShowHTML.java to produce an applet that is a password-protected gateway to a particular portion of your Web site.
  7. (More challenging) Create a client/server pair of programs that use datagrams to transmit a file from one machine to the other. (See the description at the end of the datagram section of this chapter.)
  8. (More challenging) Take the VLookup.java program and modify it so that when you click on the resulting name it automatically takes that name and copies it to the clipboard (so you can simply paste it into your email). You’ll need to look back at the IO stream chapter to remember how to use the Java 1.1 clipboard.

[68] This means a maximum of just over four billion numbers, which is rapidly running out. The new standard for IP addresses will use a 128-bit number, which should produce enough unique IP addresses for the foreseeable future.

[69] Many brain cells died in agony to discover this information.

[70] This section was contributed by Robert Castaneda, with help from Dave Bartlett.

[71] This section was contributed by Bill Venners (www.artima.com).

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:03/13/2000