My first idea was to use the Simple Object Access Protocol (SOAP). But the current W3C specification and implementation of the SOAP API from Apache presented only the HTTP binding for SOAP. Because of this, the SOAP services had to be deployed in a Web server container. The possibility of opening up a SOAP-based interface to the existing RMI services was limited by the following factors:
The services were interacting with many legacies and in turn with the native programs, which could not be accessed from the Web server system for various security reasons.
We could have implemented a proprietary XML-based solution to overcome the problem. I chose to use SOAP because it's slowly emerging as the new standard for XML-based distributed computing. Moreover, by following a standard, it's easier for other applications to use the services without any particular knowledge of their internals.
In this article, we want to develop a framework that soap-enables the existing RMI services by keeping the interfaces intact so they can still be used as normal RMI services. In addition, the framework will demonstrate how to write a SOAP message handler that will receive a SOAP request document, parse it to invoke the requested service, and construct a SOAP response document. We'll also develop a mechanism to specify, deploy, and configure the services with the help of a deployment descriptor file. Then we'll build a client program to access these services.
The framework will be developed by defining a very simple RMI service; we'll be using the Apache SOAP implementation (version 2.2) to build a SOAP client to use the RMI services. Note: This framework is just one approach that can be used to talk SOAP over RMI. (The source code can be downloaded from below. )
Anatomy of SOAP
SOAP provides a simple and lightweight mechanism to exchange structured information in a decentralized and distributed environment. In essence, it's a model for encoding data in a standardized XML format for use in a variety of situations, such as messaging and remote procedure calls (RPC). SOAP consists of three essential parts:
1. Envelope: This is the top-level XML element or the root element in an XML-encoded SOAP message. The Envelope may contain any or all of the following information: the recipient of the message, the content of the message, and the processing instructions for the message.
2. Encoding rules: These specify the way the application-defined data-type instances will be exchanged.
3. RPC: These define a convention for representing the remote procedure calls and the responses to them.
To understand the three parts of the SOAP message let's look at Listing 1. This listing shows a typical SOAP message in an HTTP request binding. The Envelope element is namespace qualified (SOAP-ENV) in order to separate it from any other application-specific identifier. It also contains information about namespace encoding. The immediate child element of the envelope in this example is the Body element. This element must be present in a SOAP message; it holds the information about the name of the remote procedure (GetLastTraderPrice) to be invoked and also encodes the parameters required by the remote procedure. In this particular message the remote procedure requires the name of the company (DIS) from which to retrieve the last trade price. Optionally, the Body element contains information about any error that occurred during the RPC and is encoded in a Fault element. The Fault element contains the error/status information with a SOAP message and, if present, can appear only once as a child element of the Body element.
In complex cases, the Envelope element may contain another child element named Header. If this element is present in a SOAP Envelope, it must be the immediate child of the Envelope element. The Header element can typically hold information regarding authentication, transaction management, and more. In our particular example, we would not be using this header information.
A SOAP response document contains a similar structure (see Listing 2). The Envelope element contains the Body element, which in turn contains the requested information, or a fault string, should there be any fault generated during the service call.
For the purpose of this article we'll develop a remote procedure call framework to invoke RMI services, and we'll be using and dissecting the three parts of the SOAP specification.
Conceptual Framework for Sending SOAP Messages over RMI
The basic idea of how to achieve SOAP over RMI relies on the fact that if we can send and receive SOAP request and SOAP response documents over the wire using RMI protocol, we'll be able to achieve our goal. In the process, we'll create a SOAP Envelope document, serialize it, and send it to an RMI service that will decode the SOAP Envelope message, invoke the specified method on the specified Remote object, construct a Response object containing the return value or a fault string in case there's a problem, and again serialize it back to the caller (see Figure 1). The caller then parses the received SOAP response document and processes the result or the fault string accordingly. As the Apache SOAP implementation defines and implements the SOAP-HTTP binding, the objects provided in the API are not serializable in the context of Java RMI, and there lies the challenge - to overcome the present limitation until a more standard implementation of SOAP-RMI comes up.
Since the requirement is to continue using RMI services and to add a SOAP interface around them, we need to build a SOAP wrapper around the existing RMI services. Also, we need to build a SOAP client following the Apache SOAP implementation. But at the moment the Apache SOAP implementation handles HTTP binding only, so we need to extend it so it can call RMI services. Also, on the RMI server side, we need some mechanism to configure, deploy, and load these SOAP-aware services. Remember, not all the RMI services may need a SOAP interface, so you should be able to specify and configure the services that are required to be SOAP-enabled. Thus, the following components need to be developed:
The SOAP Client
- SOAP wrapper
- SOAP service manager
- Configuration file - the deployment descriptor
- Apache SOAP extension (this will be a custom Call object, as we will see)
- SOAP client
It's unusual to start with the last component specified in the list, but it helps me explain the flow better. As mentioned earlier, we'll be using the Apache SOAP API version 2.2 to write a SOAP client. It's beyond the scope of this article to provide a complete introduction and tutorial of the API, but I'll scheme through the necessary sections as and when required.
First, it's important to understand that to invoke a remote service deployed as a Web service over HTTP or to invoke a remote service over RMI protocol, as we'll be doing, we're essentially making a remote procedure call (RPC). In an RPC, we make a call to the remote procedure and receive a response back. The API provides us with a call and a response object. Also, we often need to pass a few parameters to invoke the remote procedure, and these parameters can be encapsulated in the Parameter object.
The main task is to provide the Call object with the Universal Resource Locator or the URI of the remote service, the name of the method to be invoked as a part of the remote call, and the parameters required by the remote method. The parameters may frequently be complex user-defined data types and objects such as a Person or an Address object. It's also required to define a serialization and deserialization mechanism for these complex objects. By default, the API provides default serializers and deserializers for data types such as Boolean, Double, Float, and Vector. If the data type is complex and user-defined, as an implementer of the SOAP client we have to define our own serializer and deserializer and register it with SOAP by setting a SOAPMappingRegistry for the particular data type.
The term serialization/deserialization as used in the SOAP context is different from the serialization notion of Java RMI. In the SOAP context, serialization in a broader term means the XML encoding and decoding of the data types. Again, our example focuses on how to implement these remote procedure calls over RMI and use simple data types to avoid extra work in defining serializers and deserializers for complex data types.
The Call object implemented in the API is tailored to the HTTP binding of SOAP. However, as explained earlier, we want to perform the RPC over RMI. Thus it's required to customize the Call object and extend it as RMICallObject and build the mechanism to achieve the RPC over RMI.
The Envelope object in the API is not serializable to perform an RMI operation. Hence the first step in our framework will be to reproduce a SOAP Envelope element in the form of an XML Document object, which is serializable in the RMI context.
public class RMICallObject extends Call
public Response invoke(URL url, String s) throws SOAPException
private Document buildDocument()throws IllegalAddException
private Response buildResponse
(Document doc) throws Exception
This class structure represents the RMICallObject class that's designed to handle the RMI call. The class overrides the invoke() method from the Call class and defines two private helper methods to construct the Document object, which will be serialized over the RMI call, and another method to construct a Response object out of the returned Document object from the RMI call. For ease of use, I've used the JDOM API from Apache for XML data handling.
The invoke() method takes the URL of the configuration file where the location and name of the RMI server is specified and looks up and obtains a remote reference of the same. As we'll see later, this RMI service will act as an entry point to all other RMI services we want to access (see Listing 3).
SOAPRMIInterface is the remote interface that the RMI service implements in order to parse the Document object that's passed as a result of the SOAP call. The implementation of the SOAPRMIInterface is given a binding in the RMI registry under the name assigned through the variable server-Name.
A Document object is constructed out of the generated Call object, which represents the SOAP Envelope.
//construct a Document object from the Call object entries
doc = buildDocument();
After obtaining a remote reference of the RMI service, execute the remote method named "invokeMethod()" on this reference by passing the Document object to it. This method is responsible for parsing the Document object and acts as the gateway to all the SOAP RMI services.
//calling the invoke method
responseDoc = obj.invokeMethod(doc);
Once the Document object is handed over to the remote service, the client-side process waits for a return Document object from the RMI server. The remote server object processes the passed Document object.
Once we've extended the Call object to the RMICallObject and incorporated the functionality to perform an RMI call, the rest of the client programming follows what we do for a normal SOAP client (RMIClient.java) (see Listing 4).
This listing is an example of the SOAP client. First, we obtain the reference to an RMICallObject, then pass the target remote service name as it's bound to the RMI registry, pass the name of the remote method to be invoked, create the Parameter object to pass the required parameters to the remote method, and, finally, invoke the remote method by calling the invoke() method on the RMICallObject. It's important to note that we pass the URL as the address of the configuration file (conf.txt) that holds the name and location of the SOAP server manager. As shown in Listing 4, the conf.txt file should be placed under the current working directory.
Once this URL is passed to the invoke() method of the RMICallObject class, it constructs a Properties file out of the given configuration file conf.txt and an RMI lookup URL with the server name and the location.
Once the returned XML Document object is obtained from the RPC, we can analyze the entries of the Document to obtain the returned result of the method call or the fault string associated with this method call, and construct the Response object out of that to return it to the Caller RMI client.
A major part of building this framework is to develop a SOAP wrapper around RMI services, which can parse and handle incoming SOAP requests. So, the basic idea of this framework is to serialize the SOAP request over RMI in the form of an XML Document object, and the SOAP wrapper is required to be an RMI service that can accept the serialized Document object. As a matter of fact, this is an RMI service acting as an entry point to all other RMI services deployed as SOAP-enabled, so it's vital to ensure that this service is up and running so other services can be accessed and used.
First, declare a Remote interface for this RMI service.
public interface SoapRMIInterface extends Remote
invokeMethod(Document doc) throws RemoteException,
Our SOAP wrapper RMI service will implement this interface, and, as a result, define the invokeMethod() method. As we can see, apart from throwing the usual RemoteException, this service also throws a customized RMISoapException to differentiate between normal RMI failures and any SOAP-related ones. This design is guided by the fact that to build a Response object, we need to pass the proper fault string information back to the caller object.
The implementation of the invokeMethod() method must perform the following tasks:
Create and return a response XML document to the caller as an RPC response once the service is successfully invoked. After receiving this XML document, the RMICallObject then constructs the Response object out of it and passes it to the SOAP client object.
This process is large in scope; the source code for SoapRMIServer is available on www.sys-con.com/java/sourcec.cfm. To check if the method exists for a given service, the service uses the Java reflection mechanism. It also uses this mechanism when it constructs a Response XML Document object out of the returned value of the service, in case the returned value is an object. While creating a response XML document to maintain the SOAP response specification, it uses the received SOAP request document to maintain the integrity of the namespace and encoding style used.
The Deployment Descriptor
SOAP utilizes an XML file called deployment descriptor to supply information to the SOAP runtime environment. The deployment descriptor contains the Universal Resource Name (URN) for the service, the name of the method, the Java implementation class if the SOAP service is deployed as a Java class, and more regarding serialization and deserialization (in a SOAP context and not in an RMI context). The amount of information that can and should be supplied depends on the SOAP runtime requirements. In the example we're following, which is a simplistic model, we need information regarding the RMI services that are deployed as SOAP-enabled services.
Typically, our deployment descriptor(s) will contain the following information:
The other bits of information that normally become a part of the SOAP service, such as the scope of the service (application/session, etc.), are not relevant to the framework we're developing, because in this particular framework I've tried to avoid the complications of session management.
- The URN of the service as the name that binds it to the RMI registry
- The name of the Java class deployed as the specified service
- The method that's exposed as the interface to the service
- Whether or not the method is implemented as static
The deployment descriptor is typically encoded within a top-level element <isd:service>, which is namespace-qualified to avoid conflict with any other user-defined entity. An example of a deployment descriptor is as follows:
<isd:provider type="java" scope="Application" methods="sayHello">
<isd:java class="Greeting" static="false"/>
This descriptor particularly defines a service named "greetingserver" with an exposed method "sayHello", and the implementation Java class of the service is "Greeting" and the method is nonstatic.
The basic idea is to provide an XML file in which we can describe the deployed services. To deploy all the services in a single file, I've put the individual descriptors under a root element called "<soapservices>" (soap.xml).
The Apache SOAP implementation provides a utility class called DeploymentDescriptor to load and parse the deployment descriptor files. I've used the same to load and access my consolidated deployment descriptor file, along with a ConfigDescriptor class designed to parse the file and retrieve everything or a descriptor for a particular service against its service ID.
The Service Manager
So far on the server side we've created the SOAP wrapper RMI service, which is the gateway to all other SOAP-enabled RMI services, and have also devised a deployment descriptor to specify and describe all the services that are to be deployed and loaded as SOAP-enabled. As a precondition to these specific services working, we need to load all the services specified in the deployment descriptor file along with the wrapper RMI service and bind them to the RMI registry. Binding is straightforward. The only difficulty I faced was in using the DeploymentDescriptor class of the Apache SOAP API, as it doesn't accept the fact that more than one service can be specified within the same descriptor file.
I designed a ConfigDescriptor class (ConfigDescriptor.java) that parses the deployment descriptor XML file and creates an individual DeploymentDescriptor object by taking each child node of the <soapservices> root element. I added a few convenient methods to retrieve all or one particular DeploymentDescriptor object given the service ID (the URN) of the service. The SoapServerManager class uses the ConfigDescriptor object and retrieves all the DeploymentDescriptor objects specified in the descriptor file. It then finds all the Java classes specified as the service implementation and binds them to the RMI registry with the names specified in the ID attribute of the <isd:service> element.
This brings all the specified RMI services up and running, with all the methods specified in the deployment descriptor file exposed as SOAP-accessible remote service methods. Once this is done, the last job of the SoapServerManager class is to load the SOAP wrapper RMI service. The additional functionality of starting up an individual service or taking down one or all of the services can be provided through the SoapServerManager class.
A Simple RMI Service
Consider a simple RMI service. This service is named "addressserver" and the remote service method is "getAddress", which takes two parameters - the name and surname of a person. Given the name and surname, the service returns the person's address. To make it a very small example service, I've hard-coded the address inside the code, although in real life this service might interact with a database and fetch the address. I've created an Address object that holds the name, city, and zip code. The service returns the Address object in response to a SOAP call (AddressService.java) (see Listing 5).
This listing explains the structure of the addressserver service, which is also designed as an RMI service.
Running the Server
To run the server, we need to ensure the following: