ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Generics and Method Objects
Pages: 1, 2, 3, 4

Adding Generic Exceptions to the Command Object Framework

As I mentioned above, it would be absolutely great to be able to use a type variable to indicate a variable length number of exceptions. Unfortunately, this is not possible in the current generics specification. The best we can do is to use a single type variable, which is usually E, and then use as narrow a class as possible when creating instances. Let's look once more at AbstractRemoteMethodCall



    public abstract class AbstractRemoteMethodCall<T, E extends Exception> {
        public T makeCall() throws ServerUnavailable, E {
        // ...
        }

        protected abstract T performRemoteCall(Remote remoteObject) throws RemoteException, E;
        // ...
    }

In TranslateWord, we narrow T and E as follows:

    public class TranslateWord extends ServerDescriptionBasedRemoteMethodCall<Word, CouldNotTranslateException>> {
        private Word _sourceWord;
        private Language _targetLanguage;
        public TranslateWord(ServerDescription serverDescription, Word sourceWord, Language targetLanguage) {
            super(serverDescription);
            _sourceWord = sourceWord;
            _targetLanguage = targetLanguage;
        }

        protected Word performRemoteCall(Remote remoteObject) throws RemoteException, CouldNotTranslateException {
            Translator translator = (Translator) remoteObject;
            return translator.translate(_sourceWord, _targetLanguage);
        }
    }

And then, in ClientFrame, TranslateWord is used in a single place, inside a single try/catch block:

            try {
                Word result = translateMethod.makeCall();
                resultText = result.toString();
            }
            catch (CouldNotTranslateException CNTE) {
                resultText = COULD_NOT_TRANSLATE_STRING;
            }
            catch (ServerUnavailable  SUA) {
                resultText = SERVER_UNAVAILABLE_STRING ;
            }
/*
    the old version of Translate word had to catch a generic exception here
            catch (Exception e) {
                resultText = e.toString();
            }
*/

            finally {
                _resultsPanel.setText(resultText);
            }

The point of this is that invocation to makeCall only needs to catch the exceptions that are actually thrown by makeCall. Using a type variable lets us narrow the range of thrown exceptions: the superclass' ServerUnavailable and E become E becomes CouldNotTranslateException in TranslateWord. The value of eliminating catch (Exception e) should not be underestimated.

There is one final wrinkle to consider: suppose the method on the server doesn't throw any exceptions (other than RemoteException). What should E be declared as? Well, one trick, which I'm simultaneously proud to have come up with and ashamed to use, is to bind the type variable E to RuntimeException, as in the following definition of GetRegistryContents.

    public class GetRegistryContents extends AbstractRemoteMethodCall<Collection<String>, RemoteException> {
        //...

        protected Collection<String> performRemoteCall(Remote remoteObject) throws RemoteException {
        // ...
        }

        // ...
    }

This actually works! RuntimeException is a subclass of Exception and so the compiler is happy (recall that E was defined to extend Exception). At the same time, RuntimeException explicitly does not need to be caught, and so the client code doesn't wind up having extraneous catch blocks. Indeed, here's the code from TranslatorPanel that uses GetRegistryContents:

        GetRegistryContents getRegistryContents = new GetRegistryContents (_registryServerName, _registryPort);
        try {
            Collection<String> serverNames = getRegistryContents.makeCall();
            model.setContents(serverNames);
        }
        catch (ServerUnavailable logged) {
            ExceptionLog.reportException(logged);
            showRemoteErrorMessage();
        }

Note: Another interesting facet of GetRegistryContents is that the type variable T is set to Collection<String>. Generics can be used recursively.

Summary

This article is the last one (for now) in this series. In the first article, I introduced a command object framework to encapsulate remote method calls in RMI. In the second article, I extended the command object framework to seamlessly implement a local stub cache behind the scenes. In doing so, I also removed the lookup code from various places in the client, instead putting most of it into a new abstract command object, ServerDescriptionBasedRemoteMethodCall. And at this point, the cost-benefit analysis looks like the following:

Related Reading

Java RMIJava RMI
By William Grosso
Table of Contents
Index
Sample Chapter
Full Description

Pro

  • Encapsulation is good. Encapsulating method calls in separate objects makes the main program logic easier to read. ClientFrame is easy-to-read code and all the details of of the remote call have been placed in a separate location.

  • ServerDescription is logically clean. We've now put the naming and lookup code in a single place and replaced calls to the RMI registry with creating ServerDescription objects. Remote method invocations now consist of creating a server description and passing it to a command object.

  • Latency has been reduced. Stubs are fetched once and then cached locally.

  • Difficult logic is implemented once, in the framework. The retry logic is implemented once, in an abstract base class, and is correct.

  • The client code is simple. The code required to create a new subclass of ServerDescriptionBasedRemoteMethodCall is almost trivial -- it's both easy to write and easy to see (at a glance) that it's correct.

  • The framework provides hooks. We have some very nice hooks for using different retry strategies based on context. We also have some very nice hooks for inserting logging functionality.

Con

  • Indirection is confusing. Extra classes that encapsulate requests and the attendant level of indirection can be confusing. For small applications, using command objects can feel like overkill.

It's a long list of benefits versus a single complaint. And, just between us, I don't think the complaint is all that justified.

William Grosso is a coauthor of Java Enterprise Best Practices.


Also in this series:

Learning Command Objects and RMI -- O'Reilly's Java RMI author William Grosso introduces you to the basic ideas behind command objects by providing a translation service from a remote server and using command objects to structure the RMI made from a client program.

Seamlessly Caching Stubs for Improved Performance -- In Part 2 of this RMI series, William Grosso addresses a common problem with RMI apps -- too many remote method calls to a naming service. In this article he extends the framework introduced in Part 1 to provide seamless caching of stubs.


Return to ONJava.com.