Distinct ONC RPC/XDR for Java
- XML Extensions
1. Overview
In contrast to ONC RPC that has
been designed for local networks, XML-RPC is a remote procedure call protocol
that works over the Internet. An XML-RPC message is an HTTP-POST request. The
body of the request is in XML.
The XML Extensions of Distinct
ONC RPC/XDR for Java (in the following referred as JRPC-XML) turn an ONC RPC
server into a web service. They provide the following features:
- XML-RPC
clients and servers can be generated from ONC RPC (XDR)-Files.
- Client
and server-side APIs are unchanged (i.e. it enables an extremely simple
transition from ONC RPC to XML RPC)
- Each
JRPC-XML server is accessible simultaneously via ONC RPC and XML-RPC.
- Automatic
generation of an XML-RPC-to-ONC-RPC-translator (i.e. direct integration of a
legacy ONC RPC server into an XML-based infrastructure)
JRPC-XML does this by
extending the Distinct ONC RPC for Java with additional support for XML.
These extensions include:
- The
ability of Jrpcgen to generate XML-RPC client stubs for a given XDR-description
(.x-file) of an ONC RPC service and to extend the existing ONC RPC client
and server classes with new methods that allows you to use them as XML-RPC
services.
- A
new serialization scheme and a type mapping that allows to encode the RPC
types not only to XDR transfer syntax but also to XML.
There are a number of
different scenarios in which JRPC-XML provides a unique tool for developing
up-to-date Web-Services reusing existing code and skills:
Scenario 1: Exposing an ONC-RPC server to the
Web
In many cases a required
functionality is implemented for many years by a robust and well-tested ONC RPC
server. The only new requirement is that this functionality should be exposed to
the Internet, possibly to clients that are not yet known and possibly designed
and deployed by other organizations. This reveals two problems: ONC RPC is a
protocol that is inherently hard to be run on the Internet. XML-RPC would be a better protocol for this job.
It fits perfectly in the Internet and client libraries are available for nearly
any programming environment. JRPC-XML can expose an existing ONC RPC server to
the Internet as an XML-RPC server with just 10 lines of java code.
Scenario 2: Easy replacement of ONC RPC by
XML-RPC
If you have existing ONC RPC
code that is
mature and known to work, JRPC-XML allows you simply to replace the protocols
without further porting effort.
Scenario 3: Smooth introduction of new
XML-RPC-based clients
JRPC-XML also allows you to write
new XML-RPC based client applications that interact with the same ONC RPC server
that also services older ONC RPC clients.
Scenario 4: Easy development of new
XML-RPC-based applications
Finally, JRPC-XML is just an
easy way of writing XML-RPC client/server applications. While the main idea of
XML-RPC is its simplicity, it lacks two major features that are important for
any more larger client/server project: it has no interface definition language
and, thus, no automatic generation of client and server stubs. This handicap
becomes obvious as soon as more complex data-types are involved and hand-coding
the subs turn out to be an error-prone and time-consuming task. Here JRPC-XML
provides an easy solution, as it reuses the well-accepted XDR language for
exactly this purpose. Moreover, with JRPC-XML you don't even need to know about
the details of XML and XML-RPC to write an XML-RPC service. If you are used
to writing ONC RPC applications with Java, you can use your skills to write an
XML-RPC service with no additional effort.
The
following sections will explain the new Jrpcgen compiler options, and give a
step-by-step illustration of the development of a Web-Service (and a client to
it), and describe the new type-mapping.
2. New Jrpcgen Options
With the introduction of
XML-RPC in JRPC-XML Jrpcgen now has two additional options:
|
-x
|
Enables XML-RPC support. Without this option only standard ONC RPC stubs
and types are generated. With this option set, Jrpcgen add the following
features to the generated classes:
| o |
A new class <interfacename>_xml.java is generated. It
contains the client stub for the XML-RPC Web-Service. |
| o |
Each standard ONC RPC server
stub now also implements the
org.apache.xmlrpx.XmlRpcHandler interface, i.e. provides an implementation for
its execute() method. I.e. it can be used directly for providing the server
functionality not only over ONC RPC but also at the same time over XML-RPC. |
| o |
Each standard ONC RPC client stubs now also implements the
org.apache.xmlrpx.XmlRpcHandler interface, i.e. provides an implementation for
its execute() method. I.e. it can be used as a handler that receives an XML-RPC
and calls out using ONC RPC. |
| o |
Each generated interface type class now implements the
com.distinct.rpc.XMLRPCType interface, i.e. it provides an implementation for
its obj_encode() and obj_decode() methods that convert the Java class to an
XML-structure and vice versa. |
|
|
-X
<packagename>
|
Specifies
the exact name of the xmlrpc-package. This enables you to select from the various
available implementations. Default is "org.apache.xmlrpc" and it is a
requirement that the package is functionally equivalent to this package. But
there are other implementations available, like the well-known "helma.xmlrpc"
that are derived from the same source and can be used instead. |
3. Development of a Web-Service
Given the following simple XDR
interface description "admin.x" of something like a rudimentary
membership administration system, we now want to create a client and a server
that use XML-RPC for communication. Note that the Search procedure in this
sample illustrates two advanced features of Distinct ONC RPC for Java, namely
the ability to handle multiple arguments in an RPC call and the attributes [in],
[out], and [inout] that specify the direction in which the parameter is used for
data transfer (both features are not present in many other implantations, but
they are fully conforming to the standard protocol). These features are
seamlessly supported also by JRPC-XML.
|
enum stat {active,
passive, dont_know};
struct member
{
string name<>;
int born_in;
stat status;
};
struct member_list
{
member
m;
member_list
*next;
};
program MEMBER_SERV
{
version
MEMBER_SERV_VERSION
{
void Add(member) = 1;
bool Search(string<> name, [out] member m) = 2;
member_list List(void) = 3;
} = 1;
} = 100000;
|
Compiling this file with
Java Jrpcgen -n -S -x -X
helma.xmlrpc admin.x
results in the creation of
eight new files: admin.java, adminServer.java, admin_xml.java, stat.java,
member.java, member_list.java, Search_1_argument.java, and
Search_1_return.java. The general usage of these files for creating an ONC
RPC client/server application has been described before and has not changed.
What is new is the file admin_xml.java and some additional methods in the
other classes.
In order to write a XML-RPC
client instead of an ONC RPC client there is one marginal modification required
at the client:
The client object is now
initialized like this:
public
static void main (String args[]) throws Exception
{
try
{
admin_xml cl = new admin_xml("http://aubach:8080/");
…..................
.....................
The new class <interfacename>_xml.java
provides the same RPC call interface to the client application as the
standard client class <interfacename>.java, but it calls using the
XML-RPC protocol. It is initialized via the URL (as String or URL type) plus
optionally with a string, giving the handler name at the server. In the Java
implementation of XML-RPC a method-name in a call usually consists of a pair
handler.procedure. This allows one server to host and distinguish multiple,
possibly unrelated RPC services at the same time. The handler name used by
default is always the <interfacename>, "admin" in this
sample.
The remainder of this client
sample is straight forward and has no differences compared to an ONC RPC client
application.
|
import java.net.*;
import helma.xmlrpc.*;
import java.util.Vector;
import com.distinct.rpc.*;
public class MemberClient
{
public static void main (String args[]) throws Exception
{
try
{
admin_xml cl = new admin_xml("http://aubach:8080");
member m1 = new member("Joe", 1965, new stat(stat.active));
cl.Add_1(m1);
member m2 = new
member("Bill", 1973, new stat(stat.active));
cl.Add_1(m2);
member m3 = new
member("Bob", 1942, new stat(stat.passive));
cl.Add_1(m3);
member m_ret = new member();
if
(cl.Search_1(args[0], m_ret))
System.err.println (m_ret.name+", born in "+m_ret.born_in);
else
System.err.println ("can't find "+args[0]);
member_list ml = cl.List_1();
}
catch (Exception x)
{
System.err.println (x);
}
}
}
|
For implementing an XML-RPC server,
there are three different possibilities in JRPC-XML:
1) Built-in HTTP server
The
XML-RPC library provides its own built-in HTTP server. This is not a general
purpose web server, its only purpose is to handle XML-RPC requests. The HTTP
server can be embedded in any Java application with a few simple lines:
WebServer webserver = new WebServer
(server port);
webserver.addHandler ("examples", someHandler);
A
special feature when using the built in Web server is that you can set the IP
addresses of clients from which to accept or deny requests. This is done via the
following methods:
webserver.setParanoid (true); //
deny all clients
webserver.acceptClient ("192.168.0.*"); // allow local access
webserver.denyClient ("192.168.0.3"); // except for this one ...
webserver.setParanoid (false); // disable client filter
If
the client filter is activated, entries to the deny list always override those
in the accept list. Thus, webserver.denyClient ("*.*.*.*") would
completely disable the web server.
For
the sample from above, a server that uses the built-in HTTP server can be
initialized like this:
|
import com.distinct.rpc.*;
import helma.xmlrpc.*;
public class MemberServer extends
adminServer
{
member_list my_list;
public static void main (String args[])
{
XmlRpc.setKeepAlive (true);
try
{
WebServer
webserver = new WebServer (8080);
MemberServer serv
= new MemberServer(false);
webserver.addHandler
("admin", serv);
System.err.println
("started web server on port 8080");
}
catch (Exception x)
{
System.err.println
("Error creating web server: "+x);
}
}
//
Constructor that creates the RPC server
public MemberServer(boolean doONC)
throws RPCError
{
super(0, doONC, doONC, true);
}
........
........
|
The code first initializes a
generic XML-RPC WebServer object, then a MemberServer (the user-derived RPC
server that implements the "admin" interface), and finally it adds the
RPC to the list of handlers for the WebServer. This can be done, as each server
class now implements the XmlRpcHandler interface and provides an execute()
method that decodes and dispatches the incoming XML-RPC calls to the remote
procedure call implementations.
The sample implements a
constructor with one Boolean that selects whether an ONC RPC server should be
started. If so, the same server object handles both RPCs coming from XML-RPC and
those coming over the classic ONC RPC protocol (using the setMultithreaded()
method of each server it can be controlled whether these calls should be handled
concurrently or should be serialized before entering the remote procedure
implementation). The full example
server is included in the sample directory.
2) Servlet
An
JRPC-XML component can be embedded into any Web server framework that supports
the servlet API. The full code that is required to run the MemberServer sample
service from a servlet is given below:
|
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import com.distinct.rpc.RPCError;
import helma.xmlrpc.*;
public class MemberServlet extends
HttpServlet
{
public XmlRpcServer xmlrpc;
public void init(ServletConfig config) throws ServletException
{
try
{
MemberServer serv = new MemberServer(false);
xmlrpc = new XmlRpcServer ();
xmlrpc.addHandler
("admin", serv);
}
catch (RPCError e)
{
throw new ServletException(e);
}
}
public void
doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
byte[] result = xmlrpc.execute (req.getInputStream
());
res.setContentType("text/xml");
res.setContentLength (result.length);
OutputStream output =
res.getOutputStream();
output.write (result);
output.flush ();
}
}
|
Similar
to the constructor in the previous stand-alone version in this sample the server
is initialized in the init() method and than added as a handler to the
XmlRpcServer object. The code in the doPost() method is pretty generic. It
simply forwards the input to the XmlRpcServer object's execute() method and
writes its output to the servlet's OutputStream.
3) Using Proxies
Often
it is not the ONC RPC server itself, that should be implemented by a stand-alone
server or a servlet, but a proxy or a gateway to the actual server. This proxy
takes XML-RPC requests, translates them to ONC RPC protocol and forwards it to
the real server. The reply from the server is then processed in a similar way in the
other direction. The server itself might be implemented using Distinct ONC RPC
for Java, but it also might be a legacy ONC RPC server written in an arbitrary
language running on arbitrary operating system.
This
typical case is supported by JRPC-XML in quite the same manner as shown in the
previous servlet sample. The only difference is that the handler that is
added to the XmlRpcServer object is not an ONC RPC server but a client, as shown
in the following sample:
|
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.net.*;
import com.distinct.rpc.RPCError;
import helma.xmlrpc.*;
public class MemberProxyServlet
extends HttpServlet
{
public XmlRpcServer xmlrpc;
public void init(ServletConfig
config) throws ServletException
{
try
{
admin cl = new
admin(InetAddress.getByName("aubach"), true);
xmlrpc = new XmlRpcServer ();
xmlrpc.addHandler
("admin", cl);
}
catch (RPCError e)
{
throw new ServletException(e);
}
catch (UnknownHostException e)
{
throw new ServletException(e);
}
}
public void
doPost(HttpServletRequest req, HttpServletResponse res)
...........
...........
|
Of course, the same approach is
possible also for a stand-alone server. Again, simply replace the ONC RPC server
object by a client and add it as a handler to the XmlRpcServer.
4. Type Mapping
XML-RPC has a very simple
type-system. The XML-RPC specification says:
Payload format
The
payload is in XML, a single <methodCall> structure.
The
<methodCall> must contain a <methodName> sub-item, a string,
containing the name of the method to be called. The string may only contain
identifier characters, upper and lower-case A-Z, the numeric characters, 0-9,
underscore, dot, colon and slash. It's entirely up to the server to decide how
to interpret the characters in a methodName.
For
example, the methodName could be the name of a file containing a script that
executes on an incoming request. It could be the name of a cell in a database
table. Or it could be a path to a file contained within a hierarchy of folders
and files.
If
the procedure call has parameters, the <methodCall> must contain a <params>
sub-item. The <params> sub-item can contain any number of <param>s,
each of which has a <value>.
Scalar <value>s
<value>s
can be scalars, type is indicated by nesting the value inside one of the tags
listed in this table:
| Tag
|
Type |
Example |
| <i4>
or <int>
|
four-byte
signed integer
|
-12
|
| <boolean>
|
0
(false) or 1 (true)
|
1
|
| <string>
|
ASCII
string
|
hello
world
|
| <double>
|
double-precision
signed floating point number
|
-12.214
|
| <dateTime.iso8601>
|
date/time
|
19980717T14:08:55
|
| <base64>
|
base64-encoded
binary |
eW91IGNhbid0IHJlYWQgdGhpcyE=
|
If
no type is indicated, the type is string.
<struct>s
A
value can also be of type <struct>. A
<struct> contains <member>s and each <member> contains a
<name> and a <value>. Here's an example of a two-element <struct>:
<struct>
<member>
<name>lowerBound</name>
<value><i4>18</i4></value>
</member>
<member>
<name>upperBound</name>
<value><i4>139</i4></value>
</member>
</struct>
|
<struct>s
can be recursive, any <value> may contain a <struct> or any other
type, including an <array>, described below.
<array>s
A
value can also be of type <array>. An
<array> contains a single <data> element, which can contain any
number of <value>s. Here's an example of a four-element array:
<array>
<data>
<value><i4>12</i4></value>
<value><string>Egypt</string></value>
<value><boolean>0</boolean></value>
<value><i4>-31</i4></value>
</data>
</array>
|
<array>
elements do not have names. You can mix types as the example above illustrates.
<arrays>s can be recursive, any value may contain an <array> or any
other type, including a <struct>, described above.
|
XDR
Type
|
XML-RPC
Transfer
|
Java
Type
|
|
(unsigned) int
|
<i4>
|
int
|
|
(unsigned) long
|
<i4>
|
int
|
|
(unsigned) short
|
<i4>
|
short
|
|
(unsigned) char
|
<i4>
|
char
|
|
(unsigned) hyper
|
<struct> of two
<i4>s
(high and low)
|
long
|
|
Float
|
<double>
|
float
|
|
Double
|
<double>
|
double
|
|
Bool
|
<boolean>
|
Boolean
|
|
String
|
<string>
|
String
|
|
Opaque
|
<base64>
|
byte array
|
|
fixed length array
|
<array>
|
Java array
|
|
variable length array
|
<array>
|
Java array
|
|
optional data
(pointer-like *x)
|
optional filed
|
reference to an object of
class x. If x is a basic type a special wrapper class XDRx is used instead
|
|
enum x
|
<i4>
|
class x with member
variable int value and one constant int per enum constant
|
|
struct x
|
<struct> with
components for each struct member.
|
class x with member
variables for each struct member
|
|
union x
|
<struct> with
components for each union member including discriminant.
|
class x with member
variables for each union member including discriminant. No overlaying of
members is supported (neither for type conversion nor for saving space)
|
|
typedef x y
|
Replacement of types
|
class x with member
variable “value” of the redefined type y
|
Compared with ONC RPC's XDR it is
apparent that XML-RPC has a shortage of all "short" integer types, all
sorts of unsigned values, hypers, single precision floats and unions. Most of
these limitations do already apply when mapping the XDR-types to Java (e.g. no
unsigned values) and encoding the "smaller" type in the
"bigger" one does (e.g. a char in a 4-byte integer) not cause a
problem as long as the stubs decode it correctly. The only new severe problem
arises with hypers. In order not to lose the complete upper four bytes we decode
it into a <struct> with two <i4> members that
represent the "high" and the "low" part of the value.
Given this mapping the usage of
XML-RPC as the transport instead of ONC RPC's XDR doesn't change anything in the
usage of the types from the Java source. The only visible difference is that all
types (other than the basic types) that are used in RPCs now also implement the
com.distinct.rpc.XMLRPCType interface (in addition to inheriting from
com.distinct.rpc.XDRType). The interface com.distinct.rpc.XMLRPCType has two
methods:
public interface XMLRPCType
{
/**
* Decodes an object of this class
from the XMLRPC standard format.
* @exception RPCError When the call
fails for any reason.
*/
public void obj_decode(Object obj)
throws RPCError;
/**
* Encodes an object of this class to
the XMLRPC standard format.
* @exception RPCError When the call
fails for any reason.
*/
public Object obj_encode();
}
Similar to xdr_encode() and
xdr_decode() are controlling the serialization of a type to and from and
XDRStream, obj_encode() and obj_decode() are controlling the serialization to
XML. By using the Apache XML-RPC implementation for Java as a basis for the
XML-coding, it is enough to provide the right Java object for encoding it
correctly to a stream. In the other direction the Apache XML-RPC implementation
directly provides the objects parsed from an XML-input. This mapping of Java
objects to XML-RPC types is described in the following table:
|
XML-RPC data type
|
Types in xdr_encode()/xdr_decode()
|
|
<i4> or <int>
|
java.lang.Integer
|
|
<boolean>
|
java.lang.Boolean
|
|
<string>
|
java.lang.String
|
|
<double>
|
java.lang.Double
|
|
<dateTime.iso8601>
|
java.util.Date
|
|
<struct>
|
java.util.Hashtable
|
|
<array>
|
java.util.Vector
|
|
<base64>
|
byte[ ]
|
This means obj_encode() and
obj_decode() basically have to map basic types to their wrapper objects (i.e.
int -> java.lang.Integer), structs and unions to java.util.Hashtable (of
"name"- "value" pairs for each member), and arrays to
java.util.Vector.
A sample of the encoding and
decoding of a more complex RPC interface type (as created by Jrpcgen) is given
below for the class member.java as defined by the example:
|
/**
*
Encodes an object of class member to the XML-RPC standard representation.
*
@return The object that
contains the encoded variable.
*/
public Object obj_encode()
{
Hashtable h__oncint = new Hashtable();
h__oncint.put("name", name);
h__oncint.put("born_in",
XDRObj.obj_encode_int(born_in));
h__oncint.put("status",
status.obj_encode());
return h__oncint;
}
/**
*
Decodes an object of class member from the XML-RPC standard representation.
*
@param obj The
object that contains the encoded variable.
*
@exception RPCError When the calls fails for any reason.
*/
public void obj_decode(Object obj)
throws RPCError
{
try
{
Hashtable h__oncint = (Hashtable)obj;
name
= ((String)h__oncint.get("name"));
born_in =
XDRObj.obj_decode_int(h__oncint.get("born_in"));
status = new stat();
status.obj_decode(h__oncint.get("status"));
}
catch (Exception e)
{
throw new RPCDecodeError("Wrong
Parameter Type");
}
}
|
As a sample of the XML
data-structures generated by JRPC-XML, here the request of a Search_1() call from
the membership administration system in the previous section is given. Please
note, that this is already an advanced example, as the second parameter of
Search_1() is defined in the .x-file to be an [out] parameter. This is the
reason why JRPC-XML handles is in the reply rather than in the request message:
member m_ret = new member();
boolean b = cl.Search_1(args[0],
m_ret));
results in a call:
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
<methodCall>
<methodName>admin.Search_1</methodName>
<params>
<param>
<value>Bob</value>
</param>
</params>
</methodCall>
|
The reply looks like this:
|
<?xml
version="1.0" encoding="ISO-8859-1" ?>
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>ret2</name>
<value>
<struct>
<member>
<name>born_in</name>
<value>
<int>1942</int>
</value>
</member>
<member>
<name>status</name>
<value>
<int>1</int>
</value>
</member>
<member>
<name>name</name>
<value>Bob</value>
</member>
</struct>
</value>
</member>
<member>
<name>ret1</name>
<value>
<boolean>1</boolean>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse> |
The
Search_1() call actually replies a structure consisting of two components: the
real boolean return value (ret1) and the member output parameter (ret1). This
conversion of output parameters to return structs is already a feature of
Distinct ONC RPC for Java and it is supported by the XML-RPC support as well.
This product includes software
developed by the Apache Software Foundation (http://www.apache.org/).
|