Example 11-7. A reusable XML-RPC server
package javaxml2;
import java.io.IOException;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.WebServer;
public class LightweightXmlRpcServer {
/** The XML-RPC server utility class */
private WebServer server;
/** Port number to listen on */
private int port;
/** Configuration file to use */
private String configFile;
public LightweightXmlRpcServer(int port, String configFile) {
this.port = port;
this.configFile = configFile;
}
public void start( ) throws IOException {
try {
// Use Apache Xerces SAX Parser
XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");
System.out.println("Starting up XML-RPC Server...");
server = new WebServer(port);
// Register handlers
} catch (ClassNotFoundException e) {
throw new IOException("Error loading SAX parser: " +
e.getMessage( ));
}
}
public static void main(String[] args) {
if (args.length < 2) {
System.out.println(
"Usage: " +
"java com.oreilly.xml.LightweightXmlRpcServer " +
"[port] [configFile]");
System.exit(-1);
}
LightweightXmlRpcServer server =
new LightweightXmlRpcServer(Integer.parseInt(args[0]),
args[1]);
try {
// Start the server
server.start( );
} catch (IOException e) {
System.out.println(e.getMessage( ));
}
}
}
Nothing remarkable here. The code ensures that the required
parameters are passed in and then starts the server on the requested
port. It's now time to add in methods to load the handlers from
a file, and then add those handlers one by one to the server.
Because each handler needs a name and an associated class, you can
create a configuration file that has these two pieces of information.
With Java, it is easy to load and instantiate a class with its
complete package and name. This means you can completely represent a
new handler with a pair of textual values. Within this file, you can
add both the original HelloHandler class as well
as the new Scheduler class. Since you are writing
the file parser as well, it's safe to arbitrarily decide to use
commas as delimiters and the pound sign (#) as a
comment marker. In fact, you can use whatever format you wish as long
as you write code that uses your conventions in parsing the file.
Example 11-8. XML-RPC configuration file
# Hello Handler: sayHello( )
hello,javaxml2.HelloHandler
# Scheduler: addEvent(), removeEvent(), getEvents( )
scheduler,javaxml2.Scheduler
For documentation purposes, I've specified the methods
available to each handler in comments. This allows future maintainers
of this configuration file to know what methods are available for
each handler.
Java's I/O classes make it easy to load this file and read its
contents. It's simple to create a helper method that reads the
specified file and stores the pairs of values in a Java
Hashtable. The object can then be passed on to
another helper that loads and registers each handler. This example
method does not do extensive error checking as a production-ready
server might, and it simply ignores any line without a pair of
comma-separated values. It is easy to add error handling if you want
to use this code in your applications. Once it finds a line with a
pair of values, the line is broken up and the class identifier and
class name are stored as an entry within the
Hashtable. Add the import statements for the
required utility classes and then the new getHandlers(
) method to the LightweightServer class
now:
package javaxml2;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Hashtable;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.WebServer;
public class LightweightXmlRpcServer {
// Existing method implementations
private Hashtable getHandlers( ) throws IOException {
Hashtable handlers = new Hashtable( );
BufferedReader reader =
new BufferedReader(new FileReader(configFile));
String line = null;
while ((line = reader.readLine( )) != null) {
// Syntax is "handlerName, handlerClass"
int comma;
// Skip comments
if (line.startsWith("#")) {
continue;
}
// Skip empty or useless lines
if ((comma = line.indexOf(",")) < 2) {
continue;
}
// Add the handler name and the handler class
handlers.put(line.substring(0, comma),
line.substring(comma+1));
}
return handlers;
}
}
Instead of adding code to save the result of this method, you can use
that result as input to a method that iterates through the
Hashtable and adds each handler to the server. The
code needed to accomplish this task is not complicated; the only
notable thing is that the addHandler( ) method of
WebServer requires an instantiated class as a
parameter. The code is required to take the name of the class to
register from the Hashtable, load that class into
the JVM with Class.forName( ), and then
instantiate that class with newInstance( ). This
is the methodology used in class loaders and other dynamic
applications in Java, but may be unfamiliar to you if you are new to
Java or have not had to dynamically instantiate classes from a
textual name before. Once the class is loaded in this way, it and the
class identifier are passed to the addHandler( )
method, and the iteration continues. Once the contents of the
Hashtable are loaded, the server is set up and
ready to go. I've used the Enumeration class
to cycle through the keys in the Hashtable, so
you'll need to add this import statement to your source file:
package javaxml2;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import helma.xmlrpc.XmlRpc;
import helma.xmlrpc.WebServer;
public class LightweightXmlRpcServer {
// Existing method implementations
private void registerHandlers(Hashtable handlers) {
Enumeration handlerNames = handlers.keys( );
// Loop through the requested handlers
while (handlerNames.hasMoreElements( )) {
String handlerName = (String)handlerNames.nextElement( );
String handlerClass = (String)handlers.get(handlerName);
// Add this handler to the server
try {
server.addHandler(handlerName,
Class.forName(handlerClass).newInstance( ));
System.out.println("Registered handler " + handlerName +
" to class " + handlerClass);
} catch (Exception e) {
System.out.println("Could not register handler " +
handlerName + " with class " +
handlerClass);
}
}
}
}
This is simply a complement to the getHandlers( )
method; in fact, it takes the result of that method as input. It uses
the String values within the
Hashtable and registers each. Now the server is
running and will have any handlers in the configuration file loaded
and available for remote calls. You could just as easily have
consolidated these methods into one larger method. However, the
purposes of the two methods are significantly different; while one,
getHandlers( ), deals with parsing a file, the
other, registerHandlers( ), deals with registering
handlers once information about the handlers is available. With this
methodology, you can change the way you parse the configuration file
(or even have it read from a database or other medium) without having
to worry about the way the handlers are registered.
Once you have added these two helper methods, add their invocation to
the start( ) method of your server class:
public void start( ) throws IOException {
try {
// Use Apache Xerces SAX Parser
XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser");
System.out.println("Starting up XML-RPC Server...");
server = new WebServer(port);
// Register handlers
registerHandlers(getHandlers( ));
} catch (ClassNotFoundException e) {
throw new IOException("Error loading SAX parser: " +
e.getMessage( ));
}
}