macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

jRendezvous: Java and Rendezvous Working Together
Pages: 1, 2

Implementation Example: Integrating Rendezvous with Tomcat

Let's check where we are now. We've reviewed Rendezvous, how Services are named, provided a patched version of jRendezvous, and explored jRendezvous's important classes and interfaces. We're ready now to do something interesting.



With Safari, Apple's browser, users can discover and browse web sites on their local network. One way to do this is to use Apple's mod_rendezvous_apple, already bundled with their Apache distribution. To experience this, display Safari's bookmarks and select the Rendezvous Collection from the left. We'll use this to test our Tomcat integration.

Rendezvous in Safari Screenshot
Rendezvous in Safari Screenshot

Why should mod_rendezvous_apple users have all the fun? With jRendezvous and Jakarta's Tomcat Web Server and Servlet container, you can make Java powered web sites become Rendezvous services.

Servlet containers have the concept of a Context, which is a logical grouping of server resources. For instance, all of Justin's files and images might be under the /~justin context. Fortunately, Tomcat has the org.apache.catalina.core.StandardContext class that encapsulates this concept nicely. We will thus advertise Servlet contexts as Rendezvous services.

We first create a subclass of StandardContext called RendezvousContext. This class will have a static Rendezvous member that all ServiceInfo objects will register with. The entire Tomcat instance should have only one Rendezvous instance running within it, so keeping it static ensures this no matter how many RendezvousContexts are running.


public class RendezvousContext extends StandardContext {

    private static Rendezvous rendezvous = null;

    private int port = 0;
    private InetAddress address = null;
    
    static {
        try {
            rendezvous = new Rendezvous();
        } catch(IOException ioe) {
            ioe.printStackTrace();
            rendezvous = null;
        }
    }

    public RendezvousContext() {
        super();
    }
    
    ...

The method of StandardContext that is analogous to the init() method is setParent(Container parent). For explanations of Tomcat's Containers, consult their source or javadocs. For our purposes, it's only important to understand that setParent is called as the object instance is finished being setup by the container. It is here we will create the instance of ServiceInfo to advertise this context.


public void setParent(Container parent) {
    super.setParent(parent);

    createRendezvousService();
}

private void createRendezvousService() {
    if (rendezvous == null) {
        return;
     }

    Hashtable props = new Hashtable();
    props.put("path", getPath() + "/");

    try {
        ServiceInfo serviceInfo = new ServiceInfo(
                "_http._tcp.local.", getPath(),
                getAddress(), getPort(), props,
                getAddress().getHostName());

        rendezvous.registerService(serviceInfo);
    } catch(IOException ioe) {
        ioe.printStackTrace();
    }
}

The above code sets a "path" property to the context's base path. For instance, if this was a context for a user's home directory, the path property would be set to "/~user". This path property is a standard property for HTTP Rendezvous services. Safari uses this path property to properly setup a URI from the service advertisement. The StandardContext class provides a method called getPath() that provides the path of the context. We also use the getPath method to name the service, for lack of a better name. A future version of this context could get its name from the Tomcat configuration file.

The most difficult piece of the RendezvousContext was determining, at runtime, what the server's address and port number are. The service advertisement must be able to advertise where the server is hosted. The StandardContext class does not have any methods that directly provide this information. Normally, a servlet context does not care what IP addresses or port numbers are hosting it. The context might even be hosted via several addresses.

Luckily, the Tomcat API is very flexible; with a little hunting we can find a way to discover an IP address and port number that is hosting the context. Tomcat uses Connectors to physically connect contexts to the outside would. The connector might connect via HTTP/1.1, or through some other protocol to another web server. We want to advertise servlet contexts that can be reached via HTTP/1.1, so we specifically look for those connectors. Below is an example of one way to obtain a connector that is hosting this context. A more advanced version of this code would set up a service advertisement for each connector.


private CoyoteConnector getCoyoteConnector() {
    CoyoteConnector coyoteConnector = null;

    if (getParent() instanceof Host &&
            getParent().getParent() instanceof Engine) {

        Engine engine = (Engine) getParent().getParent();
        Service service = engine.getService();
        Connector[] connectors = service.findConnectors();
        for (int i = 0; i < connectors.length; i++) {
            Connector connector = connectors[i];
            if (connector instanceof CoyoteConnector &&
                ((CoyoteConnector)connector).getProtocolHandlerClassName()
                    .toUpperCase().indexOf("HTTP") != -1) {

                coyoteConnector = (CoyoteConnector) connector;
                break;

            }
        }
    }

    return coyoteConnector;
}

Once we have a reference to the connector, we can find the port and IP address that the connector is listening to.


private int getPort() {

    if (this.port == 0 && getCoyoteConnector() != null) {
        this.port = getCoyoteConnector().getPort();
    }

    return this.port;
}

private InetAddress getAddress() {
    if (this.address == null && getCoyoteConnector() != null) {
        String addr = getCoyoteConnector().getAddress();
        if (addr == null) {
            try {
                this.address = InetAddress.getByName(
                        InetAddress.getLocalHost().getHostName());
            } catch(UnknownHostException uhe) {
                uhe.printStackTrace();
            }
        }
    }

    return this.address;
}

That's all the code required to advertise a Tomcat servlet context as a Rendezvous service. The entire source is available for download, along with a compiled class version.

Installation of this class is quite straight forward. Place the compiled class file (RendezvousContext.class) into Tomcat's $TOMCAT_HOME/server/classes directory. Place the modified, bug fixed jrendezvous.jar into $TOMCAT_HOME/server/lib.

Finally, we must configure Tomcat to use this class when constructing contexts. Use the contextClass attribute of the Context element in the Tomcat configuration to specify the custom RendezvousContext class. Below is the configuration for hosting user directories via Tomcat with the Rendezvous functionality. Note that it is specific to Mac OS X, due to its use of the /Users directory. The O'Reilly article Top Ten Tomcat Configuration Tips explains this and other configurations very well.


<Listener className="org.apache.catalina.startup.UserConfig" 
directoryName="Sites" homeBase="/Users" 
contextClass="RendezvousContext"
userClass="org.apache.catalina.startup.HomesUserDatabase"/>

This example was tested with Tomcat 4.1.24 LE JDK1.4 on Mac OS X and Linux 2.4. The RendezvousContext might be portable over different Tomcat versions because of its usage of internal APIs that normally should be hidden from user code.

Once you startup Tomcat, use Safari's Rendezvous bookmarks to find your user's home directories. You should see two items for every user, one for Apache's native mod_rendezvous_apple, and one for your Tomcat server. Those user directories being served by Tomcat will look like /~user. If the new entries are missing, check Tomcat's catalina.out or other log files in the $TOMCAT_HOME/logs directory for any errors.

Final Thoughts

jRendezvous, once we worked through the few bugs, is a joy to work with. I find its API clean and simple. It's a great way to integrate Rendezvous service discovery and advertisement into your Java programs. I hope that it continues to be maintained.

The more I worked with Tomcat's internal APIs, the more I appreciated it. I think the Tomcat developers are doing a wonderful job in constructing a very flexible system that is both user friendly and developer friendly.

There is one small deficiency in the RendezvousContext class. It does not clean itself up correctly. It should capture a shutdown type event to correctly stop the Rendezvous instance. Otherwise, the threads inside Rendezvous continue to run. Issuing the shutdown command won't actually shutdown Tomcat. A challenge to the reader: adapt the RendezvousContext class to shutdown gracefully.

Seth Ladd is Lead Software Architect at Brivo Systems, Inc. He develops servlet and J2EE systems for REST based RDF and Ontology applications.


Return to Mac DevCenter