Using SSL With JINI 2.0

Introduction

This document covers the steps required to configure a client and server to make use of SSL under JINI 2.0. It uses a custom client (code included below) together with Blitz JavaSpaces but the steps are the same for any combination of client and service. In this particular example we will work towards a client obtaining a proxy for the service, verifying it and authenticating it. There are notes following on from the example at the end of the document which provide guidance on various other requirements such as getting the client authenticating to the server.

Comments and questions can be sent to me dan;at_dancres;dot_org.

Basic Setup

This example uses a relatively simple directory setup as follows:

blitz_ssl
    |
    |----->client_classes (contains the example client source and classes)
    |
    |----->config (contains policy files, blitz javaspaces configuration files, starter configuration files etc)
    |   |
    |   |----->client (contains client keystore, truststore etc)
    |   |
    |   |----->server (contains server keystore, truststore etc)
    |
    |----->lib (contains the blitz .jars)

For the purposes of the examples, we assume that a JINI LUS and httpd are already up and running. We're making use of a sample from the Blitz JavaSpaces distribution which starts up an instance of the service and a suitable codebase all in one configuration file to save some effort. For other services, I assume you have the codebase up and running before trying to start your service. If you need some scripts for JINI, I recommend Brian Murphy's available from here (note that Brian provides some examples of SSL/Secure setups - DON'T use those as this worked example has a different setup which isn't compatible - i.e. it won't work! :)

See the appendices for copies of some of the configuration files (I've left out some of them, such as blitz.config which contains a lot of material we aren't interested in for this example).

Compiling the Example Clients

Compiling the example clients (source code in the appendices) is relatively easy (my JINI 2.0 distribution is located in /Users/dan/jini/jini2_0/):

cd /Users/dan/blitz_ssl/client_classes/org/dancres/ssl/test

javac -classpath /Users/dan/jini/jini2_0/lib/jini-core.jar: \
                     /Users/dan/jini/jini2_0/lib/jini-ext.jar: \
                     /Users/dan/jini/jini2_0/lib/jsk-platform.jar: \
                     /Users/dan/blitz_ssl/client_classes *.java

Certificates, Keys, KeyStores and TrustStores

Before an SSL connection can be used to send and receive data, a handshake step is performed for the purposes of establishing keys to use, authenticating etc. This step requires a collection of data in the form of certificates and keys. For this example, we'll be using public key cryptography. This means that client and server will exchange public keys whilst keeping their private keys to themselves. The client uses the server's public key to encrypt messages intended for the server and the server uses the client's public key to encrypt messages intended for the client.

It is important that client and server keep their private keys secure and accessible only to them. This material is kept in a keystore along with the public material. Then we copy the public material from the keystore into a truststore to be used by the SSL layer for determining whether a connecting entity is acceptable and communicating with that entity once the connection is established.

Okay, let's generate the keystores for client and server. We're going to use the JDK's keytool for this task:

keytool -keypass clientpw -storepass clientpw -keystore client/keystore -genkey -validity 1800 -alias client
-dname CN=Client


keytool -keypass serverpw -storepass serverpw -keystore server/keystore -genkey -validity 1800 -alias server
-dname CN=Server

Now we need to extract the necessary public components from the keystores and place them in the appropriate truststores. Here's what we need to end up with:

Client_Truststore
     |
     |----->Client public key/certificate
     |
     |----->Server public key/certificate
     
Server_Truststore
     |
     |----->Server public key/certificate
     |
     |----->Client public key/certificate

In this particular case, we can see the files will be identical but it's worth keeping them separate. This is because the service may talk to multiple clients but no service other than Reggie whilst the client may talk to many other services that our example service is unaware of and thus the server truststore needn't contain any of the keys/certificates for these other services.

Certificates are of little use unless they are signed. The keytool will have generated self-signed certificates which are sufficient for this example but are no use for more public use. In these situations, one would need to arrange for the certificates to be signed by a certification authority (which could be one of the big commercial companies or you could setup your own authority. For more information on this topic, see here.

Now we'll actually create the truststores. The process is a little convoluted as we must first extract the information from the keystores and then generate truststores from that information:

// Extract the client certificate
//
keytool -keypass clientpw -storepass clientpw -keystore client/keystore -export -alias client -file client/cert

// Put a copy in the client truststore
//
keytool -keypass trustpw -storepass trustpw -keystore client/truststore -import -noprompt -alias client
 -file client/cert

// We also need to insert the client public certificate into the server
// truststore.

keytool -keypass trustpw -storepass trustpw -keystore server/truststore -import -noprompt -alias client
 -file client/cert


// Repeat for the server certificate
//
keytool -keypass serverpw -storepass serverpw -keystore server/keystore -export -alias server
 -file server/cert

keytool -keypass trustpw -storepass trustpw -keystore server/truststore -import -noprompt -alias server
 -file server/cert

// We also need to insert the server public certificate into the client
// truststore.

keytool -keypass trustpw -storepass trustpw -keystore client/truststore -import -noprompt -alias server
 -file server/cert

Server Endpoint Configuration

Whilst the client can place constraints on a service's proxy (in this case, the client would specify Integrity.YES to get some kind of encrypted connection), it is actually up to the service to provide a proxy which can meet those needs. So we must configure the service to use the appropriate transport which, in this case, is SSL. Many services including Blitz JavaSpaces have adopted an unofficial convention of allowing configuration of their endpoint via the serverExporter configuration variable. So lets modify the configuration file to use an SSL transport:

// Some of these imports aren't needed 'til later
//
import net.jini.jeri.ssl.*;
import net.jini.jeri.*;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;

import net.jini.security.*;

    .........

    private static ILFactory =
             new ProxyTrustILFactory(null, null);
    serverExporter = new BasicJeriExporter(SslServerEndpoint.getInstance(0),
                        ILFactory, false, true);

    .........

Now we can run a simple test (note I've not provided the source for BasicTest - all it does is a basic lookup and a few JavaSpaces operations, it's available in the Blitz JavaSpaces distribution but I'm assuming you have your own little lookup client for your service - if you need help writing that code, see here):

// Start Server - we've enabled some debugging for interest
// Note the argument to tell the JDK where the SSL truststore is located.

java -Djavax.net.debug=ssl -Djava.security.policy=config/policy.all
 -Djavax.net.ssl.trustStore=config/server/truststore -jar /Users/dan/jini/jini2_0/lib/start.jar
       config/start-trans-blitz_with_httpd.config         
       
// Start Client
//
java -Djava.security.policy=config/policy.all -Djavax.net.ssl.trustStore=config/client/truststore
 -classpath /Users/dan/blitz_ssl/lib/blitz.jar:/Users/dan/jini/jini2_0/lib/jini-ext.jar: \
 /Users/dan/jini/jini2_0/lib/sun-util.jar org.dancres.blitz.remote.test.BasicTest
    

Adding Support for Integrity

Now we've established that our basic configuration works, it's time to make it more real. We want the client to assert constraints which ensure that:

  1. The Service Proxy provides connectivity which will give us object integrity.
  2. The client trust's the Service Proxy - without trust, there's no guarentee that the proxy is telling the truth in respect of it's support for object integrity.

In order to achieve this we need to do the following:

  1. Modify the client to assert it's constraints and verify Service proxy validity.
  2. Modify the Service's exporter to assert constraints to ensure we provide integrity (it's possible to configure transports which can't provide integrity).
  3. Ensure that the codebase's integrity can be validated.

The source code for the new client (SSLClient.java) can be found in the appendices and handles our requirements for client changes. Now we need to change the Service's exporter:

    .........

    private static ILFactory =
             new ProxyTrustILFactory(new BasicMethodConstraints(
	                        new InvocationConstraints(Integrity.YES, null)), null);

    serverExporter = new BasicJeriExporter(SslServerEndpoint.getInstance(0),
                        ILFactory, false, true);

    .........

Now we need to ensure we have codebase integrity. This requires that we use an httpmd URL to specify our codebase location and that client and service use an appropriate protocol handler to download the codebase. The httpmd protocol adds an additional component to the URL which consists of a secure hash which can be used to verify the contents of the codebase have not been tampered with. We could do all of this statically, but we'll do it dynamically to make our lives easier. For this to work, we must modify our service start .config (see the appendices for the original) by replacing our current codebase value with:

import net.jini.url.httpmd.HttpmdUtil;

	.......
        private static codebase = HttpmdUtil.computeDigestCodebase(
                "/Users/dan/blitz_ssl/lib",
                (String) ConfigUtil.concat(new Object[] {
                                      "httpmd://", ConfigUtil.getHostName(),
                                       ":", codebasePort, "/",
                                      "blitz-dl.jar;sha=0"}));
	.......
	                                      

This uses HttpmdUtil to automatically generate the codebase annotation and substitute it at the appropriate point in our codebase URL. HttpmdUtil has a couple of methods but the one we use requires us to pass in the path to our codebase .jars and the template URL to use. Note the ;sha=0 which specifies the digest algorithm to use and also indicates the point in the URL at which substitution of the generated digest should be performed.

Now we are ready to test our new setup. First, we must restart our JINI infrastructure (I'm assuming you haven't cleared out the previous step in this example) and then we start the server as before but using our modified start .config and adding an additional command-line argument to configure the additional URL handler for httpmd:

java -Djava.protocol.handler.pkgs=net.jini.url -Djavax.t.debug=ssl
      -Djava.security.policy=config/policy.all
      -Djavax.net.ssl.trustStore=config/server/truststore -jar /Users/dan/jini/jini2_0/lib/start.jar
           config/start-trans-blitz_with_httpd.config

Now we'll run our SSLClient which uses a BasicProxyPreparer to verify the service's proxy and assert the Integrity.YES constraint. It also needs the additional command-line argument to configure the URL handler:

java -Djava.protocol.handler.pkgs=net.jini.url -Djava.util.logging.config.file=logging.properties
     -Djava.security.policy=config/policy.all -Djavax.net.ssl.trustStore=config/client/truststore
     -classpath /Users/dan/blitz_ssl/client_classes:/Users/dan/jini/jini2_0/lib/jsk-platform.jar: \
     /Users/dan/jini/jini2_0/lib/jini-ext.jar:/Users/dan/jini/jini2_0/lib/sun-util.jar
           org.dancres.ssl.test.SSLClient

Just for fun, this command-line includes a logging.properties which configures some parts of the JINI Starter Kit to output interesting debug information (see the appendices for the contents of logging.properties).

Getting the Client to Authenticate the Server

The final step in this example, is to have our client authenticate the server. To do this requires us to:

  1. Modify the client to assert additional constraints on the service proxy in the form of required constraints.
  2. Modify the service to startup with the appropriate Subject so as to be able to authenticate to the client.

The necessary changes for the client can be seen in SSLClient2.java in the appendices. Basically, the client must load the required Principal (or Principals if it'll allow the server to authenticate as one of a number of Principals) and then specify appropriate constraints (ServerMinPrincipal or ServerMaxPrincipal).

The service must be modified to perform a JAAS login as the appropriate Subject and then initialize (advertise it's proxy etc.). A typical example of the kind of code required to perform a JAAS login is:

            LoginContext myContext = new LoginContext("org.dancres.blitz.Client");
            myContext.login();

            Subject.doAsPrivileged(
                                   myContext.getSubject(),
                                   new PrivilegedExceptionAction() {
                                       public Object run() throws Exception {
                                           exec();
                                           return null;
                                       }
                                   },
                                   null);

Fortunately, most services (including Blitz JavaSpaces) provide a configuration variable to set the LoginContext and will login accordingly using code similar to the above. The configuration variable is loginContext so, let's modify the blitz.config now:

.........

     loginContext = new LoginContext("org.dancres.blitz.Server");
     
.........

This will ensure our service performs the necessary login but we still have some JAAS configuration to do. When the server attempts to authenticate, JAAS will attempt to locate a configuration block for the identified context (org.dancres.blitz.Server in this case). We must create a suitable login configuration and make it available to JAAS. For this example, we'll use the KeyStoreLoginModule which will allow us to make use of our existing server keystore for the purposes of authentication:

org.dancres.blitz.Server {
    com.sun.security.auth.module.KeyStoreLoginModule required
        keyStoreAlias="server"
        keyStoreURL="file:config/server/keystore"
        keyStorePasswordURL="file:config/server/password";
};

We'll put it in the file config/server/ssl.login. This block defines the authentication module we'll use, the identity we wish to authenticate as, the location of the keystore and a location from which the password to access the keystore can be found. Likely as not, you wouldn't configure password access this way as putting plaintest passwords in a file is not recommended! Now we need to put the password in config/server/password. We actually created this password when we generated the keystores at the start of the example:

        cat serverpw > config/server/password

We are now ready to run our new setup. As before, shutdown the previous example (if it's running) and restart the JINI infrastructure. Now we run the server as follows:

	java -Djava.security.auth.login.config=config/server/ssl.login
		 -Djava.protocol.handler.pkgs=net.jini.url -Djavax.net.debug=ssl
		 -Djava.security.policy=config/policy.all
		 -Djavax.net.ssl.trustStore=config/server/truststore -jar /Users/dan/jini/jini2_0/lib/start.jar
		 config/start-trans-blitz_with_httpd.config

Note the addition of the java.security.auth.login.config argument which tells JAAS where to look for configuration blocks in response to creation and login via LoginContext instances.

Now we can run our new client:

	java -Djava.protocol.handler.pkgs=net.jini.url
	     -Djava.util.logging.config.file=logging.properties -Djava.security.policy=config/policy.all
	     -Djavax.net.ssl.trustStore=config/client/truststore
	     -classpath /Users/dan/blitz_ssl/client_classes: \
	      /Users/dan/jini/jini2_0/lib/jsk-platform.jar:/Users/dan/jini/jini2_0/lib/jini-ext.jar: \
	      /Users/dan/jini/jini2_0/lib/sun-util.jar
	      org.dancres.ssl.test.SSLClient2

All Done

That's it, we're done. What follows are some additional notes on other relevant topics.

The Example is not Totally Secure

Whilst the client has authenticated the server and verified the code, the supplier of the proxy (Reggie) has not been authenticated or validated and that provides possibilities for a third-party style of attack. To make this example bulletproof would require taking similar steps to secure and authenticate Reggie.

Additional Client Constraints

The client should also include additional constraints when instantiating the ProxyPreparer. An obvious addition would be to include a constraint for server authentication during the verification stage such that if any part of the trust verification process must communicate remotely (e.g. to download a trust verifier), the remote process would be required to authenticate itself appropriately (see Security.verifyObjectTrust and BasicProxyPreparer).

Authenticating the Client

The process for setting up authentication of the client by the server follows a similar process to the example:

  1. Modify the client to perform a JAAS login.
  2. Generate the appropriate JAAS configuration files and run the client with the appropriate command-line arguments.
  3. Modify the server's configuration to authenticate the client (this can be done by adding appropriate constraints to the client exporter). Note that if the client is going to pass a remote reference to the server, that should be validated by configuring an appropriate ProxyPreparer (this is service specific) in the service configuration file.

Securing ServiceDiscovery

If we want to secure Reggie we will need both service and client to assert appropriate constraints. ServiceDiscoveryManager can be setup appropriately via a configuration file which the client needs to load and make available as a constructor parameter. See the JavaDoc for full details but the basic process is to configure appropriate ProxyPreparers.

Securing Service Join

If we want to secure Reggie we will need both service and client to assert appropriate constraints. Like ServiceDiscoveryManager, JoinManager can be setup appropriately via a configuration file. Typically, one adds a JoinManager configuration block to the service's configuration file (Blitz JavaSpaces supports this as do the services in the JINI starter kit) but this isn't always the case. See your service's documentation for information. See the JavaDoc for full details but the basic process is to configure appropriate ProxyPreparers.

Appendix A - Config files

start-trans-blitz_with_httpd.config

import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.config.ConfigUtil;

// Starts up Blitz in non-activatable mode with an embedded httpd to save
// starting it up separately from the command-line.  Several people suggested
// this change:  Ussama Baggili, Olaf Bergner.
//
com.sun.jini.start {
        private static codebasePort = "8081";

        private static codebase = ConfigUtil.concat(new Object[] {
            "http://", ConfigUtil.getHostName(), ":", codebasePort, "/",
            "blitz-dl.jar"});

        // Should be updated by installer
        // JINI 2.0 libs should be in this directory
        private static jiniRoot = "/home/dan/jini/jini2_0/lib/";

        // Should be edited to point at the directory containing the je.jar
        // a version of which can be found in the dbjava directory of the
	    // distribution
        //
        private static dbLib = "dbjava/je.jar";

        // The directory where you installed Blitz
        //
        private static blitzRoot = "/home/dan/src/jini/space/";

        private static blitzLib = ConfigUtil.concat(new Object[] {
                                    blitzRoot, "lib/"
                                  });

        private static separator = System.getProperty("path.separator");

        static classpath = ConfigUtil.concat(new Object[] {
            jiniRoot, "jsk-platform.jar", separator, jiniRoot, "jini-ext.jar",
            separator, jiniRoot, "sun-util.jar", separator, dbLib,
            separator, blitzLib, "blitz.jar"});

        private static config = ConfigUtil.concat(new Object[] {
            blitzRoot, "config/blitz.config"});

        private static policy = ConfigUtil.concat(new Object[] {
            blitzRoot, "config/policy.all"});

        static serviceDescriptors = new ServiceDescriptor[] {
                 // httpd
                 new NonActivatableServiceDescriptor(
                 "",
                 policy,
                 ConfigUtil.concat(new Object[] {jiniRoot, "tools.jar"}),
                 "com.sun.jini.tool.ClassServer",
                 new String[]{"-port",
                 codebasePort,
    			 "-dir", 
                 blitzLib,
		    	 "-verbose"}),

                 // Blitz
                 new NonActivatableServiceDescriptor(
                        codebase, policy, classpath,
                        "org.dancres.blitz.remote.BlitzServiceImpl",
                         new String[] { config }
        )};
}

policy.all

grant {
    permission java.security.AllPermission "", "";
};

logging.properties

handlers= java.util.logging.ConsoleHandler

.level=INFO

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter =
java.util.logging.SimpleFormatter


############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
net.jini.security.trust.level = FINEST

#net.jini.loader.pref.PreferredClassProvider.level = ALL
#net.jini.jeri.tcp.client.level = FINEST 
#net.jini.jeri.BasicInvocationHandler.level = FINEST
#net.jini.jeri.connection.ConnectionManager.level = FINEST

Appendix B - Source Code

SSLClient.java

package org.dancres.ssl.test;

import java.rmi.RMISecurityManager;

import net.jini.space.JavaSpace;

import net.jini.core.entry.Entry;

import net.jini.core.lease.Lease;

import net.jini.security.BasicProxyPreparer;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;

public class SSLClient {
    
    private void exec() throws Exception {
        Lookup myFinder = new Lookup(JavaSpace.class);

        JavaSpace mySpace = (JavaSpace)
            new BasicProxyPreparer(true,
                                   new BasicMethodConstraints(
                                   new InvocationConstraints(Integrity.YES, null)),
                                   null).prepareProxy(myFinder.getService());

        System.out.println("Find JavaSpace: " + mySpace);
    }

    public static void main(String []args){
        
        try{
            if (System.getSecurityManager() == null)
                System.setSecurityManager(new RMISecurityManager());

            /*
            LoginContext myContext = new LoginContext("org.dancres.blitz.Client");
            myContext.login();

            Subject.doAsPrivileged(
                                   myContext.getSubject(),
                                   new PrivilegedExceptionAction() {
                                       public Object run() throws Exception {
                                           exec();
                                           return null;
                                       }
                                   },
                                   null);
            
            System.exit(0);
            */

            new SSLClient().exec();

        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

SSLClient2.java

package org.dancres.ssl.test;

import java.security.KeyStore;

import java.rmi.RMISecurityManager;

import net.jini.space.JavaSpace;

import net.jini.core.entry.Entry;

import net.jini.core.lease.Lease;

import net.jini.security.BasicProxyPreparer;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.core.constraint.ServerMinPrincipal;

import com.sun.jini.config.KeyStores;

import javax.security.auth.x500.X500Principal;

public class SSLClient2 {
    
    private void exec() throws Exception {
        KeyStore myStore =
            KeyStores.getKeyStore("file:config/client/truststore",
                                  null);

        X500Principal myServerUser =
            KeyStores.getX500Principal("server", myStore);

        System.out.println("Server principle: " + myServerUser);

        Lookup myFinder = new Lookup(JavaSpace.class);

        InvocationConstraint[] myConstraints = 
            new InvocationConstraint[] {Integrity.YES, ServerAuthentication.YES,
                                        new ServerMinPrincipal(myServerUser)};

        InvocationConstraints myAllConstraints =
            new InvocationConstraints(myConstraints, null);

        JavaSpace mySpace = (JavaSpace)
            new BasicProxyPreparer(true,
                                   new BasicMethodConstraints(myAllConstraints),
                                   null).prepareProxy(myFinder.getService());

        System.out.println("Find JavaSpace: " + mySpace);
    }

    public static void main(String []args){
        
        try{
            if (System.getSecurityManager() == null)
                System.setSecurityManager(new RMISecurityManager());

            /*
            LoginContext myContext = new LoginContext("org.dancres.blitz.Client");
            myContext.login();

            Subject.doAsPrivileged(
                                   myContext.getSubject(),
                                   new PrivilegedExceptionAction() {
                                       public Object run() throws Exception {
                                           exec();
                                           return null;
                                       }
                                   },
                                   null);
            
            System.exit(0);
            */

            new SSLClient2().exec();

        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

Lookup.java

package org.dancres.ssl.test;

import java.io.IOException;

import java.rmi.RemoteException;

import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;

import net.jini.core.entry.Entry;

import net.jini.lookup.entry.Name;

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;

/**
   A class which supports a simple JINI multicast lookup.  It doesn't register
   with any ServiceRegistrars it simply interrogates each one that's
   discovered for a ServiceItem associated with the passed interface class.
   i.e. The service needs to already have registered because we won't notice
   new arrivals. [ServiceRegistrar is the interface implemented by JINI
   lookup services].

   @todo Be more dynamic in our lookups - see above

   @author  Dan Creswell (dan@dancres.org)
   @version 1.00, 7/9/2003
 */
class Lookup implements DiscoveryListener {
    private ServiceTemplate theTemplate;
    private LookupDiscovery theDiscoverer;

    private Object theProxy;

    /**
       @param aServiceInterface the class of the type of service you are
       looking for.  Class is usually an interface class.
     */
    Lookup(Class aServiceInterface) {
        this(aServiceInterface, null);
    }

    Lookup(Class aServiceInterface, String aName) {
        Class[] myServiceTypes = new Class[] {aServiceInterface};

        Entry[] myAttrs = null;

        if (aName != null) {
            myAttrs = new Entry[] {new Name(aName)};
        }

        theTemplate = new ServiceTemplate(null, myServiceTypes, myAttrs);
    }

    /**
       Having created a Lookup (which means it now knows what type of service
       you require), invoke this method to attempt to locate a service
       of that type.  The result should be cast to the interface of the
       service you originally specified to the constructor.

       @return proxy for the service type you requested - could be an rmi
       stub or an intelligent proxy.
     */
    Object getService() {
        synchronized(this) {
            if (theDiscoverer == null) {

                try {
                    theDiscoverer =
                        new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
                    theDiscoverer.addDiscoveryListener(this);
                } catch (IOException anIOE) {
                    System.err.println("Failed to init lookup");
                    anIOE.printStackTrace(System.err);
                }
            }
        }

        return waitForProxy();
    }

    /**
       Location of a service causes the creation of some threads.  Call this
       method to shut those threads down either before exiting or after a
       proxy has been returned from getService().
     */
    void terminate() {
        synchronized(this) {
            if (theDiscoverer != null)
                theDiscoverer.terminate();
        }
    }

    /**
       Caller of getService ends up here, blocked until we find a proxy.

       @return the newly downloaded proxy
     */
    private Object waitForProxy() {
        synchronized(this) {
            while (theProxy == null) {

                try {
                    wait();
                } catch (InterruptedException anIE) {
                }
            }

            return theProxy;
        }
    }

    /**
       Invoked to inform a blocked client waiting in waitForProxy that
       one is now available.

       @param aProxy the newly downloaded proxy
     */
    private void signalGotProxy(Object aProxy) {
        synchronized(this) {
            if (theProxy == null) {
                theProxy = aProxy;
                notify();
            }
        }
    }

    /**
       Everytime a new ServiceRegistrar is found, we will be called back on
       this interface with a reference to it.  We then ask it for a service
       instance of the type specified in our constructor.
     */
    public void discovered(DiscoveryEvent anEvent) {
        synchronized(this) {
            if (theProxy != null)
                return;
        }

        ServiceRegistrar[] myRegs = anEvent.getRegistrars();

        for (int i = 0; i < myregs.length; i++) {
            serviceregistrar myreg = myRegs[i];

            Object myProxy = null;

            try {
                myProxy = myReg.lookup(theTemplate);

                if (myProxy != null) {
                    signalGotProxy(myProxy);
                    break;
                }
            } catch (RemoteException anRE) {
                System.err.println("ServiceRegistrar barfed");
                anRE.printStackTrace(System.err);
            }
        }
    }

    /**
       When a ServiceRegistrar "disappears" due to network partition etc.
       we will be advised via a call to this method - as we only care about
       new ServiceRegistrars, we do nothing here.
     */
    public void discarded(DiscoveryEvent anEvent) {
    }
} 

© Copyright 2004 Dan Creswell

Back to Getting Started with JINI 2.0