Converting a Web site to a Web service requires some new tools in
your programming toolbox. For Web services that need to exchange
arbitrary Java objects, you can add the
tools described here. I'll show you how to exchange Java objects
using SOAP RPC and invoke methods on these objects.
The source code for the examples as well as the listings
discussed here can be downloaded from
below. The example code consists
of a client and a simple Web service (see Figure 1). The
client is a Java application named Client.
The Web service code is a Java class called ObjectDepot. ObjectDepot
exposes two SOAP callable methods using a deployment descriptor named
DeploymentDescriptor.xml. The method getMyObject() will return an
instance of AClass. The method getObject (String name) returns an instance of
a named class that is passed as a String parameter. BClass is used in
the example. AClass and BClass are simple do-nothing classes I've
used for demonstration purposes. The only restriction on the objects
that you send and receive is that they must implement the Serializable Interface.
The client makes a SOAP method call that
returns a Java Object. The dotted lines represent the marshall() and
by ObjSerializer. unmarshall() method calls made I used the following
tools in addition to the Java 2 Platform, Standard Edition (J2SE) to
set up my development environment:
If you are not familiar with Apache SOAP, there are several examples
of using SOAP in the sample directory of the SOAP zip file. For this
article, assume you have executed one of the SOAP Remote Procedure
Call (RPC) sample programs.
A Little Background
Any type of data returned by invoking a SOAP RPC needs to be
marshalled at the sender and unmarshalled at the receiver. Apache
SOAP uses type mappings to determine how Java data types should be
marshalled to XML so that the data can be transmitted on the network.
The same is true when the data is received at a client.
Another type mapping is used to determine how the data should be
unmarshalled. The type mappings are stored in a type-mapping
registry. The default registry is org.apache. soap. encoding.
SOAPMappingRegistry. Apache's SOAP implementation contains
serializers and deserializers that will marshall and unmarshall many
of the basic Java objects. Among the objects supported is the Java
String Object, and there is even a Java Bean serializer and
deserializer. After browsing the SOAP documentation, I wondered why
any Java object couldn't be transmitted. This was in the Apache SOAP
documentation:
"If you need to create a new serializer/ deserializer, then looking
at the source code for the predefined ones will probably provide you
with the best guidance. Remember that they will need to implement
org.apache .soap. util.xml. Seri alizer and org.apa che.soap.
util.xml. Deserializer respectively. You can implement them both in
the same class or use two different classes. It really makes no
difference."
There was my answer. I could write my own Java object serializ er/deserializer.
After digging a little further, I discovered that there are several
technical hurdles to scale. First I needed to serialize the object.
Then I'd need to fit the serialized object into the SOAP envelope.
Once the object reached the client, it would need to be deserialized
so that it could be used. The biggest hurdle was getting access to
the class file on the client so that the object could be deserialized.
My Approach
I'm sure you're aware that developers don't usually write their final
version of code on the first attempt. Instead of describing the final
version of my code, I'm going to describe how I arrived at that point.
To get started, I needed a serializer and deserializer for my Java
objects. My implementation of both is contained in Obj
Serializer.java. Objserializer implements the Apache SOAP serializer
and deserializer interfaces. I used Java's standard Object Input
Stream and ObjectOutputStream to do most of the heavy lifting (see
Listing 1).
I also used an Apache SOAP utility called Base64 to convert the
binary object data to a base64 encoded String.
byte byteArray[] = ObjectToArray(value);
Base64.encode(byteArray, 0, byteArray.length, sink);
I did this to prevent any encoding that an application server might try to do.
Wire Tapping
Once I had the serializer working, I used another Apache SOAP utility
called TcpTunnel Gui to see what was being sent over the wire. The
TcpTunnelGui utility works as a relay between the client application
and the Web server. The SOAP envelope sent by the client is displayed
in one half of the window and the reply is displayed in the other
half. Figure 2 shows a typical SOAP exchange. I could see that my
serializer was working by looking at the reply envelope. I got
several complaints from the Client code, but that was what I expected
since I hadn't written any code to handle the returned data yet.
As mentioned earlier, I saw that the deserializer on the client
machine had to have access to the object's class file for it to be
deserialized for the client to use. When I began, the only way I
could accomplish this was to copy the class file from the Web server
to a file in the class path of the client just before the class file
was needed for unmarshalling the data into the object. I added some
code to the catch block in ObjSerializer that caught the
ClassNotFoundException that was being thrown by the readObject()
method of ObjectIn putStream (see Listing 2).
In the catch block I retrieved the class
file from the Web server and then retried the unmarshalling. After
the readObj ect() method returned the object, I removed the class
file from the disk. I left my original unmarshall() method in
ObjSerial izer.java for you. The unmarshalling worked and I could use
the returned object, but I thought there must be a more elegant way.
I posted a note on
the Apache SOAP mailing list explaining what I was trying to do and
someone suggested I look at class loaders. After a little reading I
realized that URLClass Loader would do the job and I only needed to
override the findClass() method in URLClassLoader.
A Class Loader Mini-Course
The entire subject of class loaders is beyond the scope of this
article. You need, however, to know about the delegation model to
understand how my code works. A class loader has a parent class
loader. The set of a class loader and its ancestors is called a
delegation. Standard Java applications begin with a delegation of
three class loaders. The system class loader loads classes from the
class path, delegates to the extension class loader, and is used to
load Java extensions. The parent of the extension class loader is the
bootstrap class loader. The bootstrap class loader loads the core API.
When MyURLClassLoader is added to the delegation, the system class
loader becomes its parent. When a class loader loads a class, it
consults its parent first to give the parent a chance to load the
class file. Whenever a class refers to another class, the same class
loader that loaded the referencing class loads the referent. In other
words, since ObjSerializer is loaded by MyURLClassLoader, MyURLClass
Loader will be used to load classes needed by ObjSerializer. That is
done when the system class loader calls the findClass() method of
MyURLClassLoader after failing to find a class file when the
readObject() method is called from ObjSerializer's unmarshall()
method (see Figure 3). The findClass() method needs to know the host
name of the Web server and where the class files are located on the
server. These are read from ObjSerial izer.properties when
MyUrlClassLoa der is instantiated. The unmarshall() method
calls the readObject() method of Object Input Stream. Which then
calls the find Class() method of MyURLClass Loader.
The findClass() method retrieves the class file from the remote web
server. The readObject() method returns the object to the
unmarshall() method. The unmarshall() method returns the object in a
SOAP Bean.
Here are the contents of my ObjSerializer. properties file:
ClassDefHost=localhost
ClassDefPort=8080
ClassDefDirectory=/objser
Listing 3 from MyURLClass Loader builds the URL for the class file
when it's needed (the class file we need is called name). I stored
AClass. class and BClass. class in a directory named objser under
Tomcat's webapps directory.
As I explained earlier, I would need a Class that would load
ObjSerializer using the Class.forName() method. That way I could pass
an instance of MyURLClass Loader to be used to load ObjSerializer.
This project was getting a little complicated, but I was almost home.
I created ObjSerializerLoader.java to do the job (see Listing 4).
Okay, now I was ready. I had my SOAP deployment descriptor set up to
register ObjSerializerLoader as my serializer and deerializer on the
SOAP server. I also registered ObjSerializer Loader in Client for the
same purpose.
I started up Tomcat, ran Client which invoked ObjDepot's
getMyObject() method through SOAP RPC.
I was upset to see a ClassNotFound Exception, just as I had seen
before. It looked like MyURLClass Loader wasn't being used at all.
After a few System. out.println() statements, I discovered I was
right. MyURLClassLoa der wasn't being used. As it turned out, I had
made only one mistake. When I used the Class.f or Name() method to
create my instance of ObjSerializer, I assigned it to a variable of
type ObjSerializer. Since the system class loader could find the
class file for ObjSerializer, the system class loader was used to
create the object and not my class loader. What I needed to do was
put ObjSerializer into a variable of type Object and use Java
Reflection to invoke the marshal() and unmarshall() methods (see
Listing 5).
That way there would be no reference to ObjSerializer in
ObjSerializerLoader, so my code would compile without ObjSe rializer
in the class path and the system class loader would delegate to MyURL
ClassLoader's find Class() method when ObjectSerializer is
unmarshalling the returned data. As I mentioned earlier, the
delegation takes place when the read Object() method is called in the
unmar shall() method of ObjSerializer (see Listing 6).
If you're like me, you've spent some time figuring out class path
problems when some class file couldn't be found. In this case, I
didn't want ObjSerializer to be found by the system class loader.
Try, Try Again
After a few modifications, I was ready to go again. When I tried the
client this time, it worked. I had sent a Java object across the
network using SOAP RPC and executed that object on the client machine
by using Java Reflection to invoke the main() method of the returned
object. This was done without having any reference to the class file
on the client machine. Life was good.
A word about security: as powerful as this tool can be, it can also
be dangerous. This tool should only be used among trusted Web sites.
You should also consider encryption, based on the sensitivity of the
data transmitted.
Running the Example Code
Here is what you will need to do to run the example code.
- On the client workstation, create a directory called Client.
- Extract Client.java, ObjSerializer.java, Obj
SerializerLoader.java, MyURLClass Load er.java, Deployment Desciptor
xml, Obj ectSerializer.properties, and wssetup.bat into the client
directory.
- On the Web service workstation, which can be the same as the
client workstation, create a directory called "objde pot". Extract
IObjDepot.java, ObjDepot. java, Aclass.java, Bclass.java, MyURLCl
assLoader.java, and wssetup.bat into the obj depot directory.
- Edit wssetup.bat and change the line that modifies the
classpath to point to the location of your various toolkit .jar
files. You'll notice a remark at the top. You can cut and paste this
line to your command line. When executed, this will add ObjDepot to
the Apache SOAP deployed services. You can access the Apache SOAP
Admin Web page at: http://localhost:8080/soap/admin/index.html. That
way you can see that the Web service is properly deployed.
Removing from the Depository
There is also a remarked-out line at the bottom of the batch file
that can be used to remove ObjDepot from the Apache SOAP depository.
- From a command window, change to the client directory and
execute wssetup.bat. Compile the Java files in the client directory.
Now go to the objdepot directory and compile the Java files there.
- Create a directory called "objser" under Tomcat's webapps directory.
- Move (not copy) ObjSerializer.class, ACla ss.class, and
BClass.class to the objser directory.
- Modify your tomcat.bat file to include the objdepot directory
in the classpath.
- Start Tomcat.
- From the client directory, run Client.
You should see indications that AClass was received and BClass executed.
Create Your Own
If you download the code, read the comments, and refer back to this
article, you should be able to figure out how to use SOAP to transfer
Java objects from your Web service and use them on a client.
Author Bio:
Wyn Easton, an advisory software engineer with IBM, has worked with
Java for four years and Web applications for two. He has a patent
application for some of his work with XML and Java. weaston@us.ibm.com
SOAP on a ROPE, by Wyn Easton
WSJ Vol 01 Issue 04 - pg.30
Listing 1
public byte[] ObjectToArray(Object obj) throws
Exception
{
ObjectOutputStream oos = null;
try
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
oos = new
ObjectOutputStream(bos);
// serialize
the object passed in
oos.writeObject(obj);
oos.flush();
return
bos.toByteArray();
}
catch (Exception e)
{
System.out.println("Exception in ObjToArray = " + e);
throw(e);
}
finally
{
oos.close();
}
}
Listing 2
try
{
ByteArrayInputStream bin = new
ByteArrayInputStream(Base64.decode(value));
ois = new
ObjectInputStream(bin); // Create an Object Input Stream
// return the
new object
Object object
= (Object)ois.readObject();
return new
Bean(Object.class, object);
}
catch (ClassNotFoundException cnfe)
{
System.out.println("Class Not Found Exception in unmarshall = " +
cnfe);
// this is
where I was getting the class file originally
}