Overview

This text contains a user's guide and a quick walkthrough of the current implementation. Some of the classes in the library requires JDK 1.2 and 1.3, but the core functionality can be used with JDK 1.1. The documentation clearly states which JDK is required for each class.

This document is incomplete and will be updated during April 2001

The library consists of four major parts:

Server Can be used in a servlet environment or as a stand-alone server accepting HTTP posts containing XML-RPC messages. The server uses the parser to interpret the XML-RPC messages and the serializer to convert return values to their XML-RPC counterparts. A secure server supporting Secure Sockets Layer (SSL) will be available during the summer 2001, or so.
Client Connects to an XML-RPC server and sends and receives XML-RPC messages and responses. It uses the serializer to convert call arguments and the parser to interpret return values.
Parser Uses any SAX compliant driver to parse XML-RPC messages and extracting the values contained therein.
Serializer Converts Java objects into their XML-RPC counterparts. Custom serializers are used for converting objects not inherently known to the basic serializer.

Users will come in contact with the serializer when specifying which custom serializers to be used during the serialization process. The library contains several custom serializers for serializing Java 2 collections, arrays, and other types of objects. Users will also come in contact with the parser when specifying which SAX driver to use during parsing. All other interaction occurs through the server and the client.

Serializers

When sending XML-RPC messages from an XmlRpcClient to an XML-RPC server and from an XmlRpcServer to an XML-RPC client, Java objects need to be translated into their XML-RPC representations. Servers need to translate return values from their handlers, and clients need to translate arguments supplied in method calls. This is accomplished by using serializers.

You do not need to know how the serialization mechanism works in order to use the XML-RPC server or client. Unless you're interested in creating your own custom serializers, you may skip the rest of this section.

XmlRpcSerializer

All serialization occurs initially through the XmlRpcSerializer class which has support for basic object types like java.lang.String, java.lang.Integer, and so forth (see list below). If the XmlRpcSerializer is passed an object which it does not recognize, it keeps a list of XmlRpcCustomSerializers which it tries to use instead. Each custom serializer reports which kind of Java object it knows how to serialize.

  • int
  • String
  • boolean
  • double
  • java.util.Date
  • java.util.Hashtable
  • java.util.Vector
  • byte[]
The XmlRpcSerializer class supplies a static serialize() method through which all Java objects are serialized. This applies when using custom serializers as well. The serialize() method converts the supplied object and appends the XML-RPC representation in the supplied string buffer.

    public static void serialize(
        Object value,
        StringBuffer output );
            
By using custom serializers, Java objects not inherently supported by the XmlRpcSerializer may be handled as well. Custom serializers are registered and unregistered through the static registerCustomSerializer() and unregisterCustomSerializer() methods.

    public static void registerCustomSerializer(
        XmlRpcCustomSerializer serializer );

    public static void unregisterCustomSerializer(
        XmlRpcCustomSerializer serializer );
            

XmlRpcCustomSerializer

The following class represents a custom serializer that may handle any kind of Java 2 collection which is serialized into an XML-RPC array. This serializer is included in the Marquee XML-RPC library.


    public class CollectionSerializer implements XmlRpcCustomSerializer
    {
        public Class getSupportedClass()
        {
            return Collection.class;
        }


        public void serialize(
            Value,
            StringBuffer output )
        {
            Collection c  = ( Collection ) value;
            Iterator iter = c.iterator();

            output.append( "" );

            while ( iter.hasNext() )
            {
                XmlRpcSerializer.serialize( iter.next() );
            }

            output.append( "" );
        }
    }
            
This class implements the methods introduced in XmlRpcCustomSerializer and is a complete serializer.

getSupportedClass()

The getSupportedClass() method returns the class indicating which types of objects it knows how to serialize. This method is called in two situations by the XmlRpcSerializer. First when the serializer is installed by calling XmlRpcSerializer.registerCustomSerializer(), and second when an actual object has been requested for serialization by calling the XmlRpcSerializer.registerCustomSerializer() method.

When registering a custom serializer, XmlRpcSerializer investigates the supported class so that it can determine where in the list of custom serializers the serializer belongs. For instance, if the serializer list already contains a serializer that knows how to handle java.util.Vector objects, the generic CollectionSerializer, above, will end up after the java.util.Vector serilizer. That is, the Vector serializer will override the generic CollectionSerializer, as it operates on a more specialized kind of collection.

When an object has been requested for serialization, the XmlRpcSerializer class queries every custom serializer in the list until it finds a serializer that knows how to handle the supplied value. For instance, when passing the XmlRpcSerializer.serialize() method a java.util.ArrayList object, the CollectionSerializer, if registered with XmlRpcSerializer, will catch the object (as it is an instance of java.util.Collection) and convert it into an XML-RPC array.

serialize()

Notice how the serialize() method in the CollectionSerializer above reuses the default serialization mechanism introduced in XmlRpcSerializer. The supplied collection may contain any kind of object which the XmlRpcSerializer knows how to handle, or for which a custom serializer has been registered. Theoretically, this may result in a recursive call to the serialize() method above if any of the elements in the collection is another collection.

Included custom serializers

The Marquee XML-RPC library supplies a few useful implementations of the XmlRpcCustomSerializer interface for converting generic collections and maps, as well as a few other more specialized types. It also contains a reflective serializer which can serialize any type of object by using Java Reflection.

CollectionSerializer Serializes Java 2 collections into XML-RPC arrays (requires JDK 1.2 or above)
MapSerializer Serializes Java 2 maps into XML-RPC structs (requires JDK 1.2 or above)
HashtableSerializer Serializes hash tables into XML-RPC structs
VectorSerializer Serializes vectors into XML-RPC arrays
IntArraySerializer Serializes int[] arrays into XML-RPC arrays
FloatArraySerializer Serializes float[] arrays into XML-RPC arrays
DoubleArraySerializer Serializes double[] arrays into XML-RPC arrays
BooleanArraySerializer Serializes boolean[] arrays into XML-RPC arrays
ObjectArraySerializer Serializes arrays containing any kind of object into XML-RPC arrays
ReflectiveSerializer Serializes any object into XML-RPC structs using reflection

The generic collection serializer, presented in the example above, requires JDK 1.2, as does the generic map serializer. If you don't support Java 1.2, the VectorSerializer and HashtableSerializer classes may be used instead.

The XML-RPC Server

When setting up an XML-RPC server you supply a set of objects that will receive the method calls parsed by the server dispatchers. These objects must implement the XmlRpcInvocationHandler interface which can be achieved by extending the ReflectiveInvocationHandler class or wrapping the object in a ReflectiveInvocationHandler instance, or implementing it from scratch. The methods that are to be invoked through XML-RPC must only use parameters of the following types:

  • int
  • String
  • boolean
  • double
  • java.util.Date
  • java.util.Hashtable
  • java.util.Vector
  • byte[]

An exception to this rule is when using invocation processors that modify the list of call arguments according to some set of rules. For instance, an invocation processor may add an additional transaction object for methods with names starting with "tx_". These arguments are not transported using XML-RPC and may be of any type.

Return values may be of any type supported by the built-in serializer or any of the registered custom serializers. That is, if the CollectionSerializer supplied with the XML-RPC library is registered with the server, invocation handlers may return any type of object implementing the Java 2 Collection interface.

Custom serializers are ordered by specialization. That is, the reflective serializer will always be placed last and the MapSerializer will always be placed before the CollectionSerializer, and so on. This ensures that the most appropriate serializer will be used for an object during serialization -- for instance, a HashMap will not be serialized using the generic Collection serializer if a MapSerializer is available.

Example

Setting up an XML-RPC server

This example shows how to set up a server by registering invocation handlers and processors. It also shows how to specify which SAX driver and custom serializers to use.


    import marquee.xmlrpc.*;
    import marquee.xmlrpc.serializers.*;

    class SampleHandler extends ReflectiveInvocationHandler
    {
        String getNameOfMonth( int month ) throws IllegalArgumentException
        {
            if ( month > 0 && month < 13 )
            {
                return months[ month ];
            }

            throw new IllegalArgumentException( "Invalid month." );
        }

        String[] getAllMonths()
        {
            return months;
        }

        private final static String[] months =
        {
            "January", "February", "March", "April", "May", "June", "July",
            "August", "September", "October", "November", "December"
        }
    }

    public class SampleServer
    {
        public static void main( String[] args )
        {
            XmlRpcParser.setDriver( "com.sun.xml.parser.Parser" );
            XmlRpcSerializer.registerCustomSerializer( new ObjectArraySerializer() );

            XmlRpcServer server = new XmlRpcServer();
            server.registerInvocationHandler( new SampleHandler() );

            server.runAsService( 80 );
        }
    }
    
The ObjectArraySerializer added in main() makes sure that methods returning arrays of objects (including arrays of Strings) are interpretable by the serializer.

Invocation Handlers

XML-RPC messages are parsed by the XmlRpcServer and dispatched to an XmlRpcInvocationHandler corresponding to the handler name contained in the element. The server creates an XML-RPC response based on the return value of the handler, or based on an exception thrown by the handler.

The XmlRpcInvocationHandler interface contains a single method that invocation handlers must implement. The XmlRpcReflectiveInvocationHandler class supplies a default implementation of this interface that you'll use most of the times.


    public Object invoke(
        String methodName,
        Vector arguments )
        throws Throwable;
            

XmlRpcReflectiveInvocationHandler

The easiest way to create an invocation handler is to inherit the XmlRpcReflectiveInvocationHandler class which implements the XmlRpcInvocationHandler using Java Reflection to find the method targeted by the call. The XmlRpcReflectiveInvocationHandler class may also be used to wrap a Java object in a reflective handler, if your class is already inheriting from another class.

If you look at the SampleServer above, the SampleHandler represents a complete invocation handler that extends the reflective invocation handler. This is the most common way of creating invocation handlers.

If, for some reason, you do not wish to inherit XmlRpcReflectiveInvocationHandler, you may wrap your object in a new XmlRpcReflectiveInvocationHandler. This gives the overhead of an additional object being created (although with a single reference data member);


    XmlRpcReflectiveInvocationHandler handler =
        new XmlRpcReflectiveInvocationHandler( myObject );
            

Writing your own handler from scratch

If you do not wish to use the reflective handler wich uses Java Reflection to identify which method to call, you may write your own invocation handler. This may increase performance slightly if you have a single method or only a few methods in your class.


    public class MyInvocationHandler implements XmlRpcInvocationHandler
    {
        public Object invoke(
            String methodName,
            Vector arguments )
            throws Throwable
        {
            if ( methodName.equals( "doSomethingAndReturnString" ) )
            {
                return doSomethingAndReturnString();
            }

            throw new Exception( "Method not found in handler." );
        }

        public String doSomethingAndReturnString()
        {
            return "All done!";
        }
    }
            

The XML-RPC Client

Coming soon...

Dynamic Proxies

This feature requires JDK 1.3.

An alternative way of calling server procedures is to use a dynamic XmlRpcProxy. When creating an instance of the XmlRpcProxy you specify the URL of the server which should be proxied for, and a list of interfaces the proxy should implement. The proxy may be typecast to and called through any of these interfaces, which will convert calls to XML-RPC messages that are sent to the server using the "." naming convention. The names of the interfaces and their methods should, in other words, correspond to services and procedures available on the server.

Example

When developing an application using XML-RPC servers located somewhere on the Internet, you start by expressing the services available on the servers in Java interfaces.


    interface mailToTheFuture
    {
        /**
         *  Adds a message to username's queue.
         *
         *  @param username The email address of a registered user.
         *
         *  @param password must be that user's password.
         *
         *  @param message A hashtable containing the following elements; dateTime,
         *                 messageBody, receiverMailAddress, subject.
         *
         *  @return The number of messages in username's queue.
         */

        int addMessage( String username, String password, Hashtable message ) throws Throwable;


        /**
         *  Deletes a message from username's queue.
         *
         *  @param username The email address of a registered user.
         *
         *  @param password must be that user's password.
         *
         *  @param ordinal The number of the message to remove.
         *
         *  @return An empty string.
         */

        String deleteMessage( String username, String password, int ordinal ) throws Throwable;


         /** The rest of the methods are omitted for brevity */

    }
            
Every service offered by a server that is to be used by the application is defined in its own interface. The list of interfaces are supplied to XmlRpcProxy.createProxy() along with the URL of the server, to receive an object implementing the supplied interfaces.

    Object o = XmlRpcProxy.createProxy(
        "www.mailtothefuture.com",
        "/RPC2",
        80,
        new Class[] { mailToTheFuture.class } );
            
The proxy object may be typecast to any of the interfaces.

    mailToTheFuture mttf = (mailToTheFuture) o;
    System.out.println( mttf.addMessage( "usr@mailtothefuture.com", "secret", aMessageTable ) );
            
The call to mttf.addMessage() will result in an XML-RPC message being sent to the www.mailtothefuture.com host with "mailToTheFuture.addMessage" as the included method call. The compiler may check that the arguments are of correct type and allow IDE's to perform code completion and such.