15.2 XML-RPC

. In 1998, Userland (http://www.userland.com) began working with Microsoft on a standard mechanism for RPC. This mechanism worked across systems via TCP/IP, and was called XML-RPC. The XML in XML-RPC is actually a commentary on the implementation of the protocol itself; developers using XML-RPC client libraries should be familiar with the general XML-RPC format (much as a HTTP/HTML developer should be familiar with the basic HTTP formats). However, it's possible to use XML-RPC without ever having to worry about the underlying XML messages.

One of the most popular implementations of XML-RPC was the so-called Helma XML-RPC libraries, which have since been donated to the Apache Software Foundation as Apache XML-RPC. Like all Apache libraries and projects, Apache XML-RPC is free, open source, and runs well on Mac OS X.

Before using web services on Mac OS X, I'll show you a simple XML-RPC server and client implemented with Apache XML-RPC.

15.2.1 Installation and Setup

This example builds on material taught earlier in the book, which created a web services server and a graphical client.

The xmlrpc-1.1.jar file contains the Apache XML-RPC libraries. This file is available from http://xml.apache.org/xmlrpc/ under the download binaries section (http://xml.apache.org/dist/xmlrpc/release/v1.1/xmlrpc-1.1.zip as of this writing). Once you have this file, set up the directory structure shown in Figure 15-1. You'll notice that the XML-RPC JAR file was placed in the lib directory. It is then referenced in the build.xml file shown in Example 15-1.

Figure 15-1. XML-RPC example directory structure
figs/XJG_1501.gif
Example 15-1. XML-RPC build file
<project default="compile" basedir=".">
    <property name="src" location="src"/>
    <property name="build" location="build"/>

    <target name="compile">
        <javac 
            srcdir="${src}" 
            destdir="${build}"
            classpath="lib/xmlrpc-1.1.jar"
        />
    </target>
</project>

You'll use the SimpleEdit and SimpleEditPlugin classes developed in Chapter 4 for your client-side graphical user interface.

The remaining files, XmlRpcAsynchClientPlugin.java, XmlRpcClientTestPlugin.java, and XmlRpcMiniServer.java, will be added in the next few sections.

15.2.2 XML-RPC Basics

Figure 15-2 illustrates the basic model for working with XML-RPC (and SOAP). Server APIs and client libraries hide the complexity required to make method calls across different systems. In the case of Apache XML-RPC, you'll use the built-in web server to handle your server needs. Once you create a web server (using the class org.apache.xmlrpc.XmlRpcServer ), you'll just pass in an ordinary Java object to handle the incoming requests. You will not need to interact with complex interfaces or APIs; you'll just write ordinary Java application code.

Figure 15-2. Web Services development model
figs/XJG_1502.gif

Clients, however, require slightly more preparation. You can make a remote call synchronously , in which the execution of that thread stops until a response is returned, or asynchronously , in which you pass an event notification handler that is called when the remote method call completes. The advantage of asynchronous execution is that control returns to your application immediately; the Apache XML-RPC libraries automatically create a new thread for the remote communication and let your application know (via the handler) when a result has been returned. Either way, you'll want to use the class org.apache.xmlrpc.XmlRpcClient for the actual request.

If you decide to support asynchronous clients, implement the interface org.apache.xmlrpc.AsyncCallback, which has only two methods: handleError( ) , which lets you know something went wrong, and handleResult( ), which tells you that a request returned properly.

15.2.3 A Simple XML-RPC Application

The following subsections detail the process of building up an XML-RPC application. The Apache XML-RPC framework handles much of the complexity involved in this process, leaving you to implement application-specific functionality. This means that you get to focus on your business logic, rather than the intricacies of HTTP, XML, sockets, and network programming.

15.2.3.1 XML-RPC servers

The code shown in Example 15-2 demonstrates a simple XML-RPC server. Note that this server is written to be launched from the Terminal.

Example 15-2. XML-RPC mini-server
package com.wiverson.macosbook.webservices;

public class XmlRpcMiniServer extends org.apache.xmlrpc.XmlRpcServer
{
    
    public static void main(String[] args)
    {
        System.out.print("Launching...");
        try
        {
            org.apache.xmlrpc.WebServer myWebServer = 
            new org.apache.xmlrpc.WebServer(9000);
            myWebServer.addHandler("MiniServer ", new MiniServer(  ));
            myWebServer.start(  );
        } catch (java.io.IOException e)
        {
            e.printStackTrace(  );
        }
        System.out.println("ready.");
    }
    
    public static class MiniServer
    {
        public String now(  )
        {
            synchronized(this)
            {
                try
                {
                    this.wait(5000);
                } catch (java.lang.InterruptedException e)
                {}
            }
            return new java.util.Date().toString(  );
        }
        
        public String add (String a, String b)
        {
           return "" + (new Integer(a)).intValue(  ) + (new Integer(b)).intValue(  );
        }
    }
}

You'll notice that this code is surprisingly sparse. It creates a server (assigned to port 9000) and then defines a simple Java class (MiniServer) with only two methods.

The first method, MiniServer.now( ), returns the current date and time as a String (although a this.wait( ) method causes it to take a few extra seconds to execute). The second method, MiniServer.add( ), takes two Strings, converts them to integers, and then returns the result as a String.

This class is then instantiated and has a handler attached to it through the addHandler( ) method. This method makes the object available for remote access by an XML-RPC client. Finally, start( ) does just what you would expect?it gets the server to listen for XML-RPC requests.

When you build and run the code, you won't see much?just a notice of when the server is ready to accept communication:

[Luthien:~/xmlrpc] wiverson% /usr/local/ant/bin/ant
Buildfile: build.xml

compile:

BUILD SUCCESSFUL
Total time: 5 seconds
[Luthien:~/xmlrpc] wiverson% java -classpath ./lib/xmlrpc-1.1.jar:./build 
com.wiverson.macosbook.webservices.XmlRpcMiniServer
Launching...ready.

Assuming that you've placed the xmlrpc-1.1.jar file somewhere on the JVM classpath, you can verify that the server is running with a simple command line in a new Terminal window:

[Luthien:~/xmlrpc] wiverson% java -classpath ./lib/xmlrpc-1.1.jar org.
apache.xmlrpc.XmlRpcClient http://localhost:9000/ MiniServer.add 1 2
3

The XML-RPC JAR must be on your command-line classpath (through the CLASSPATH environment variable or the -cp argument to java) for clients, in addition to being in the classpath of the JVM running the XML-RPC server. Since running the example probably will involve two Terminal windows, it might require setting the classpath in both windows before running any code.

You've now created a server that provides a programmatic API (in this case, the two methods now( ) and add( )), which is available over a network.

15.2.3.2 Synchronous XML-RPC clients

Next, add the ability to talk to this XML-RPC server to a Java application. Again, you'll build on the SimpleEdit application developed in Chapter 4, starting with a synchronous client call to the XML-RPC service.

Synchronous refers to the fact that the application waits for the remote method to return before continuing program execution. For normal, local method calls, this behavior is usually acceptable, as most operations can be completed very rapidly. However, as this exercise demonstrates, it can have undesirable side effects if the application's user interface is waiting for a remote method to complete (this is also referred to as being blocked). Still, the model is much easier and more deterministic. You always know what is going on in your application because it proceeds linearly, method invocation by method invocation, until all requests have been serviced and responded to.

Example 15-3 illustrates a simple XML-RPC client, which implements both a command-line version of the client and a SimpleEditPlugin.

Example 15-3. A synchronous XML-RPC client
package com.wiverson.macosbook.webservices;

public class XmlRpcClientTestPlugin 
    implements com.wiverson.macosbook.SimpleEditPlugin
{
    
    public XmlRpcClientTestPlugin(  )
    {
    }
    
    public static void main(String[] args)
    {
        System.out.print("Calling synch...");
        System.out.println(new XmlRpcClientTestPlugin().callRemote(  ));
    }
    
    public String callRemote(  )
    {
        try
        {
            org.apache.xmlrpc.XmlRpcClient xmlrpc = 
            new org.apache.xmlrpc.XmlRpcClient
                ("http://localhost:9000/MiniServer");
            java.util.Vector params = new java.util.Vector(  );
            return (String) xmlrpc.execute("MiniServer.now", params);
        } catch (java.net.MalformedURLException e1)
        {
            e1.printStackTrace(  );
        } catch (java.io.IOException e2)
        {
            e2.printStackTrace(  );
        }catch (org.apache.xmlrpc.XmlRpcException e3)
        {
            e3.printStackTrace(  );
        }
        return "Unable to connect.";
    }
    
    public void doAction(com.wiverson.macosbook.SimpleEdit frame,
        java.awt.event.ActionEvent evt)
    {
        frame.appendDocumentText(this.callRemote(  ));
    }
    
    public String getAction(  )
    {
        return "Test Synchronous XML-RPC";
    }
    
    public void init(com.wiverson.macosbook.SimpleEdit frame)
    {
    }
}

The actual web services work occurs in the callRemote( ) method. In this case, you make the remote invocation with the org.apache.xmlrpc.XmlRpcClient class. Specify the remote server's address and the object you wish to communicate with, and then call the execute( ) method to make the remote call. Using this class from the command line is straightforward:

[Luthien:~/xmlrpc] wiverson% java -classpath ./lib/xmlrpc-1.1.jar:./build 
com.wiverson.macosbook.webservices.XmlRpcClientTestPlugin
Calling synch...Sun Jan 12 21:03:36 PST 2003

When running this application, you'll notice that the application appears to freeze after the "Calling synch..." is echoed to the screen and before the result is written out.

Similarly, you can launch the SimpleEdit application to see the interface (as shown in Figure 15-3):

[Luthien:~/xmlrpc] wiverson% java -classpath ./lib/xmlrpc-1.1.jar:./build 
com.wiverson.macosbook.SimpleEdit com.wiverson.macosbook.webservices.
XmlRpcClientTestPlugin
Figure 15-3. XML-RPC SimpleEdit synchronous client
figs/XJG_1503.gif

You will notice that the SimpleEdit program appears to have locked up when accessing the remote service (keeping in mind the delay introduced in the server to simulate a poor network). The solution is to use asynchronous client services.

15.2.3.3 Asynchronous XML-RPC clients

This section builds a version of the client that takes advantage of the built-in support for asynchronous remote method calls. In effect, the code says "Call this remote method and keep track of the call for me. Let me know when something happens, but in the meantime I'll continue working." Simply put, it takes care of the multithreading for you, leaving you to write a simple handler that receives the notifications. This is shown in Example 15-4.

Example 15-4. An asynchronous XML-RPC client
package com.wiverson.macosbook.webservices;

public class XmlRpcAsynchClientPlugin 
    implements com.wiverson.macosbook.SimpleEditPlugin
{
    public XmlRpcAsynchClientPlugin(  )
    {
    }
    
    public static void main(String[] args)
    {
        System.out.print("Calling asynch...");
        new XmlRpcAsynchClientPlugin(  ).callRemote(null);
        System.out.println("ok...");
    }
    
    public void callRemote(com.wiverson.macosbook.SimpleEdit frame)
    {
        try
        {
            org.apache.xmlrpc.XmlRpcClient xmlrpc = 
            new org.apache.xmlrpc.XmlRpcClient
                ("http://localhost:9000/MiniServer");
            java.util.Vector params = new java.util.Vector(  );
            AsynchTimeHandler myAsynchTimeHandler = new AsynchTimeHandler(frame);
            xmlrpc.executeAsync("MiniServer.now", params, myAsynchTimeHandler);
        } catch (java.net.MalformedURLException e1)
        {
            e1.printStackTrace(  );
        } catch (java.io.IOException e2)
        {
            e2.printStackTrace(  );
        }
    }
    
    public void doAction(com.wiverson.macosbook.SimpleEdit frame,
        java.awt.event.ActionEvent evt)
    {
        this.callRemote(frame);
    }
    
    public String getAction(  )
    {
        return "Test Async XML-RPC";
    }
    
    public void init(com.wiverson.macosbook.SimpleEdit frame)
    {
    }
    
    public class AsynchTimeHandler implements org.apache.xmlrpc.AsyncCallback
    {
        public AsynchTimeHandler(com.wiverson.macosbook.SimpleEdit frame)
        {
            myFrame = frame;
        }

        private com.wiverson.macosbook.SimpleEdit myFrame = null;
        
        public void handleError(Exception e, java.net.URL uRL, String str)
        {
            e.printStackTrace(  );
        }
        
        public void handleResult(Object obj, java.net.URL uRL, String str)
        {
            if(myFrame != null)
                myFrame.appendDocumentText((String) obj);
            else
                System.out.println((String) obj);
        }
    }    
}

As you can see, this code is a bit more sophisticated than its synchronous counterpart. Most importantly, there is an additional inner class, AsynchTimeHandler, to actually process the result returned by the remote method. This inner class implements the interface org.apache.xmlrpc.AsyncCallback , with the two methods handleError( ) and handleResult( ) supporting either failures or successful completion.

15.2.4 Accessing XML-RPC Services from AppleScript

In the folder /Applications/AppleScript, you'll find a tool called Script Editor (shown in Figure 15-4) that is used to work with Apple's proprietary AppleScript language. AppleScript is Apple's standard language for handling interapplication scripting, with a long heritage on the Mac OS platform. If you're coming from a Unix background, you might be used to stringing together multiple applications with shell scripts. It may be useful to think of AppleScript as a way to create shell scripts that hook together graphical applications.

Figure 15-4. AppleScript Script Editor
figs/XJG_1504.gif

A full introduction to AppleScript is beyond the scope of this book, but it is useful to know that you can connect AppleScript to Java applications via XML-RPC. Example 15-5 shows how to connect to the XML-RPC server you created earlier using AppleScript.

Example 15-5. An XML-RPC client in AppleScript
script MiniServer
    
    on now(  )
        tell application "http://localhost:9000/"
            return call xmlrpc {method name:"MiniServer.now"}
        end tell
    end now
    
    on add(s1, s2)
        tell application "http://localhost:9000/"
            return call xmlrpc
            {
                method name:"MiniServer.add", parameters:{s1, s2}
            }
        end tell
    end add
    
end script

display dialog MiniServer's add(1, 2)
display dialog MiniServer's now(  )

Running the script causes AppleScript to display two dialogs, as shown in Figure 15-5 and Figure 15-6.

Figure 15-5. AppleScript and the XML-RPC addition functionality
figs/XJG_1505.gif
Figure 15-6. AppleScript and the XML-RPC "now" functionality
figs/XJG_1506.gif

For more information on building applications with AppleScript, visit http://www.macdevcenter.com for some excellent introductory articles.