HomeDigital EditionSys-Con RadioSearch Web Services Cd
B2B Beginning WS Business Process Management Case Studies Content Management Distributing Computing e-Business Electronic Data Interchange Enterprise Industry Insight Integration Interviews Java & Web Services .NET Portal Product Reviews Scalability & Performance Security SOAP Source Code UDDI Wireless WS Standards WS Tips & Techniques WSDL WS Editorials XML

Source Code for this article

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.

Figure 1

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.

Figure 2

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
   
  }

Additional Source Code `zip file format

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.