1.3. Advanced AXIS and JAX-RPC client: rpc operation style, serializer/deserializer howto

In this section we will test several methods returning object and (array of) object. Using complex type can be very difficult for the client side (for a rpc/encoded operation style), so the programmer should try to avoid complex type on the server side, or limit it to array of simple type and/or object containing simple type. Array of object, rpc/encoded, is a bad idea, you are going to face a lot of interoperability problems. But sometime you will have to consume such a complicated web service, for commercial or technical reason, or because you have no other choice. So I will do my best to cover these techniques too.

1.3.1. Consume a method returning an object (rpc, encoded and literal)

How to consume a remote procedure returning my RcxResponse object. This object is returned by the method status() and provide some internal value of the RCX. A wsdl excerpt will help to understand this specific object:

NoteImportant note
 

Again, these codes are for my rpc operation style web service (and you can use it as a template for your own use). Now to consume the remote procedure returning complex type for my other on-line document/literal web service go to the Section called SOAP Clients: Axis and JAX-RPC "drink" complex type (document/literal).

<!-- Here you can see the name of the complex
  type we are going to use, and also a description of the elements
  contained in our object. -->
<complexType name="rcxResponse">
  <sequence>
    <element name="battery" type="soap11-enc:float"/>
    <element name="currentTime" type="int"/>
    <element name="memory" type="int"/>
    <element name="memoryTot" type="int"/>
    <element name="status" type="string"/>
  </sequence>
</complexType>
    ...
<!-- message used for method status(): -->
  <message name="RcxReadLS_status"/><!-- no param when calling the method -->
  <message name="RcxReadLS_statusResponse"><!-- return type defined -->
    <part name="result" type="ns2:rcxResponse"/>
  </message>
    ...
<!-- operation for the method "status" -->
  <operation name="status" parameterOrder="">
    <input message="tns:RcxReadLS_status"/>
    <output message="tns:RcxReadLS_statusResponse"/>
  </operation>
    ...

    etc...

Here is a short list of the information provided by the object:

The advantage of this object is that we will get all these informations in one SOAP message. The disadvantage is we are going to use a complex type object, so we need to import in our project another file generated by WSDL2Java: RcxResponse.java. Put the file: your_local_directory/net/homeip/phonedirlux/types/RcxResponse.java in your rcxwsclient/sei directory. Edit it and make the change in bold in the listing below:

You can also use the directory structure generated by WSDL2Java, in that case copy the net/ directory structure in your project (in the directory already containing rcxwsclient/).

Now, in your RcxReadLS.java file, uncomment the line:

 // public net.homeip.phonedirlux.types.RcxResponse status() throws java.rmi.RemoteException;

and change it to:

public rcxwsclient.sei.RcxResponse status() throws java.rmi.RemoteException;

Finally, add these lines to your ClientSEI.java

  // Invoke status()
  RcxResponse respobj = objRcx.status();
  System.out.println("\nStatus() method:\n" +
	"\t- message: " + respobj.getStatus() +
	"\n\t- battery level: " + respobj.getBattery() + 
	"\n\t- memroy free: " + respobj.getMemory() +
	"\n\t- memory total: " + respobj.getMemoryTot() +
	"\n\t- current time: " + respobj.getCurrentTime() + " sec");

do not forget the import directive:

import rcxwsclient.sei.RcxResponse;

Compile, execute and that should work! So, we are not in trouble here, but we are going to see that consuming a web service rpc operation style will begin to be difficult as soon as we consume remote procedures taking/returning array of simple type, and obviously array of object. In these latter cases, rpc/encoded operation style web service are the most difficult to consume.

1.3.2. Consume a Method returning an array of simple type (rpc, encoded and literal): register your serializer/deserializer or not?

A little bit more complicated now, we are going to use a method returning an array of integer. Here the web service will simulate an array of position: (x1, y1, x2, y2, etc...). The value returned are fixed and, for the time being, do not correspond to a real position of the rover. Just use this method as a demo if you have to consume a method returning an array of simple type using this technique for a rpc operation style web service.

In fact, using a method returning an array of simple type should not be difficult, but here we use a low level api so we have to register "by hand" our serializer/deserializer for our int[] (rpc/encoded).

By consuming a remote procedure returning an array of simple type and a JWSDP non - WS-I compliant (rpc-style, encoded), we need no more file generated by WSDL2Java, but we must use a serializer/deserializer.

In the case of a web service "a little" WS-I compliant (rpc-style, literal), we don't have to register our serializer/deserializer, but we have to use one more file generated by WSDL2Java: IntArray.java (should be in the directory structure net/).

And with a rpc-style, (use of encoded) web service, my advise is to use the whole directory structure generated by WSDL2Java, so just copy the net/ structure in your project. Now you should be used to adapt the different file used by your sei client. So, if needed, uncomment the line:

// public int[] collInt() throws java.rmi.RemoteException;

in your RcxReadLS.java and be ready to add several code to your client!

Edit your ClientSEI.java file and add the code below (just after the Service object) to get the TypeMapping from my web service.


  // Start of registering serializer/deserializer for array of simple type
					
  TypeMappingRegistry registry = service.getTypeMappingRegistry();
  TypeMapping typeMapping =
	registry.getTypeMapping(SOAPConstants.URI_ENCODING);

Add the import...

import javax.xml.rpc.encoding.TypeMapping;
import javax.xml.rpc.encoding.TypeMappingRegistry;
import com.sun.xml.rpc.encoding.soap.SOAPConstants;

Before to register our int[] type, let's take a look at the wsdl (excerpt from rpc-style, encoded version):

  <!-- wsdl complexType definition for "ArrayOfInt" -->
  <complexType name="ArrayOfint">
    <complexContent>
      <restriction base="soap11-enc:Array">
         <attribute ref="soap11-enc:arrayType" wsdl:arrayType="int[]"/>
      </restriction>
    </complexContent>
  </complexType>

Now we are ready to add the following code just after our TypeMapping object

  //register Dynamically int array type

  QName intArrayTypeQname = new QName( "http://phonedirlux.homeip.net/types" ,
    "ArrayOfint"); //typenamespace and  	
										
  QName intArrayElementQname = new QName("", "int");

  CombinedSerializer intSerializer = new
    SimpleTypeSerializer(QNAME_TYPE_INT, DONT_ENCODE_TYPE, NULLABLE,
      SOAPConstants.URI_ENCODING, XSDIntEncoder.getInstance());

  CombinedSerializer intArraySerializer = new
    SimpleTypeArraySerializer(intArrayTypeQname, ENCODE_TYPE, NULLABLE,
      SOAPConstants.URI_ENCODING, null, QNAME_TYPE_INT,
      int.class, 1, null, (SimpleTypeSerializer)intSerializer);
  
  intArraySerializer = new
    ReferenceableSerializerImpl(SERIALIZE_AS_REF,
      intArraySerializer);
		
  SerializerFactory intArraySerializerFactory = new
    SingletonSerializerFactory(intArraySerializer);
  
  DeserializerFactory intArrayDeserializerFactory = new
    SingletonDeserializerFactory(intArraySerializer);					

  typeMapping.register(int[].class, intArrayTypeQname,
    intArraySerializerFactory, intArrayDeserializerFactory);		

  //end of register int array type

A little more import...

import com.sun.xml.rpc.encoding.CombinedSerializer;
import com.sun.xml.rpc.encoding.SimpleTypeSerializer;
import com.sun.xml.rpc.encoding.simpletype.XSDIntEncoder;
import com.sun.xml.rpc.encoding.SimpleTypeArraySerializer;
import com.sun.xml.rpc.encoding.ReferenceableSerializerImpl;
import javax.xml.rpc.encoding.SerializerFactory;
import javax.xml.rpc.encoding.DeserializerFactory;
import com.sun.xml.rpc.encoding.SingletonSerializerFactory;
import com.sun.xml.rpc.encoding.SingletonDeserializerFactory;
import net.homeip.phonedirlux.types.arrays.IntArray; // except if you modify the package
// and put it right in your rcxwsclient/sei directory.

Here your class should also implement SchemaConstants and SerializerConstants. So Modify your ClientSEI class declaration:

public class ClientSEI implements com.sun.xml.rpc.wsdl.document.schema.SchemaConstants,
	com.sun.xml.rpc.encoding.SerializerConstants

Then you should be able to compile without error.

OK, let's modify our ClientSEI class a last time to call the method int[] collInt(). Add the following code for a not - WS-I compliant web service (rpc-style, encoded):

  // Invoke collInt() 
  int[] arrayOfInt = objRcx.collInt();
  System.out.println("Reading array of int:");
  for(int x=0; x<arrayOfInt.length;x++)
    System.out.println("\tElement " + x + ": " + arrayOfInt[x]);

Or add the following code for a rpc-style, literal web service (in this case, no need of registering your serializer/deserializer):

  // Invoke collInt() 
  IntArray arrayOfInt = objRcx.collInt();
  System.out.println("Reading array of int:");
  for(int x=0; x<arrayOfInt.getValue().length;x++)
    System.out.println("\tElement " + x + ": " + arrayOfInt.getValue(x));

By registering your deserializer (for a JWSDP not WS-I compliant) you are now able to consume a SOAP method returning an array of simple type. Congratulations! Once you understand the technique it is easy to reproduce for an array of another simple type. The next section will explore a more "psychiatric" case: an array of object. This last technique is not used often (should be avoided by server-side developer), so if you do not need it I recommend you to skip the next section. In this case you can go directly to the Section called Dynamic Invocation Interface (DII) with JAX-RPC (RPC/encoded and document/literal in section 1.4.5), otherwise have fun with:

1.3.3. Psychiatric treatment: array of object (rpc/encoded operation style)

Here are a list of the several step to follow, to handle the array of complex type returned by the method collPos() using the SEI technique.

This section was tested with my rpc/encoded operation style web service. Some minor changes are still needed for a use with a rpc/literal style (see the preceding sections).

This complex type contain just two integer simple type (x and y) still featuring a position. Then this array of position could represent a path followed by the RCX rover. Again, the value returned by the web service are fixed and do not correspond to any real position (for the time being...).

  1. Copy the class PosCol (file: PosCol.java) generated by WSDL2Java. Again put the file: your_local_directory/net/homeip/phonedirlux/types/PosCol.java in your rcxwsclient/sei directory. Edit it and make the change in bold in the listing below:

    /**
     * PosCol.java
     *
     * This file was auto-generated from WSDL
     * by the Apache Axis WSDL2Java emitter.
     */
    
    //package net.homeip.phonedirlux.types;
    package rcxwsclient.sei;
    
    public class PosCol  implements java.io.Serializable {
       
      etc...

  2. Register the types PosCol and PosCol[] using the same technique than for the int[] type returned by the method collInt(). Except, here we will need another tool to generate a specific class: PosCol_SOAPSerializer (in bold in the listing below). The lines to add to your ClientSEI class just after the registration of the int[] are listed here:

      // start of register PosCol complex type object + array
    
      QName PosColQname = new QName("http://phonedirlux.homeip.net/types" ,
    				"PosCol");
    
      QName PosColArrayTypeQname = new QName( "http://phonedirlux.homeip.net/types" ,
                                              "ArrayOfPosCol"); //typenamespace 
    	
      QName PosColArrayElementQname = new QName("","PosCol");		
    					 
      CombinedSerializer PosColSerializer = 
        new PosCol_SOAPSerializer(PosColQname, 
        ENCODE_TYPE, NULLABLE, SOAPConstants.URI_ENCODING);
      
      PosColSerializer = new ReferenceableSerializerImpl(SERIALIZE_AS_REF, 
        PosColSerializer);
      SerializerFactory PosColSerializerFactory = new 
        SingletonSerializerFactory(PosColSerializer);
      DeserializerFactory PosColDeserializerFactory = 
        new SingletonDeserializerFactory(PosColSerializer);
    					 
      CombinedSerializer PosColArraySerializer = 
        new ObjectArraySerializer(PosColArrayTypeQname, 
        ENCODE_TYPE, NULLABLE, SOAPConstants.URI_ENCODING, 
        PosColArrayElementQname, PosColQname, PosCol.class, 1, null);
      
      PosColArraySerializer = 
        new ReferenceableSerializerImpl(SERIALIZE_AS_REF, 
        PosColArraySerializer);
      SingletonSerializerFactory PosColArraySerializerFactory = 
        new SingletonSerializerFactory(PosColArraySerializer);
      SingletonDeserializerFactory PosColArrayDeserializerFactory = 
        new SingletonDeserializerFactory(PosColArraySerializer);
    							 
      typeMapping.register(PosCol.class, PosColQname, 
        PosColSerializerFactory, PosColDeserializerFactory);
      typeMapping.register(PosCol[].class, PosColArrayTypeQname, 
        PosColArraySerializerFactory, PosColArrayDeserializerFactory);
    
      // end of register PosCol complex type object + array

    See also the import needed:

    import com.sun.xml.rpc.encoding.ObjectArraySerializer;
    
    import rcxwsclient.sei.PosCol;

    Next point we will see how to create our PosCol_SOAPSerializer class with wscompile.

  3. Here are a few things to do in order to generate our class with this tool which is, in fact, a script (wscompile).

    • If not already done, download and install JWSDP 1.3 or just JAX-RPC from java.sun.com/webservices. You will find wscompile.sh (for Linux) or wscompile.bat (for Windows) in the /bin directory of the package.

    • Create a directory where you will generate the serializer and cd to that directory.

    • In this directory create an xml file called config.xml and put it the code below. Pay attention at the url, must be the rpc/encoded version (in bold).

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration
        xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
        <wsdl location="http://www.pascalbotte.be/rcx-ws-rpc/rcx?WSDL" 
          packageName="rcxwsclient.sei"/>
      </configuration>

      Eventually, create the directories rcxwsclient/sei before executing the tool.

    • Execute the tool with the command:

      [Your_prompt]$ full_path_to_your_jaxrpc_bin/wscompile.sh -gen -keep config.xml

      (for the Linux users) or

      C:\[Your_path]> full_path_to_your_jaxrpc_bin\wscompile -gen -keep config.xml

      (for the Windows users), -gen is for generate the client and -keep is to have the source file.

    • You can now copy the file rcxwsclient/sei/PosCol_SOAPSerializer.java, in the same directory structure, in your project.

  4. Here we have to add one more import directive in our ClientSEI class

    import rcxwsclient.sei.PosCol_SOAPSerializer;

    Finally you can add the code to consume the method collPos() :

      // rpc-style "use=encoded" web service version (not WS-I compliance)
      // Invoke collPos(), return an array of PosCol
      PosCol[] ArrayOfPosCol = objRcx.collPos();
      System.out.println("Array of complex type PosCol");
      for(int y = 0; y < ArrayOfPosCol.length; y++)
        System.out.println("Pos" + y + ": x = " + ArrayOfPosCol[y].getXPos() + ", y= " + 
          ArrayOfPosCol[y].getYPos());

That's it! You can now compile and execute your project and enjoy the array of complex type returned by my rpc/encoded web service... You should have something like:

[your_prompt]$ java rcxwsclient.ClientSEI
  ...
Array of complex type PosCol
Pos0: x = 1, y= 2
Pos1: x = 3, y= 4

1.3.4. Registering "ArrayOfArrayOf" serializer/deserializer.

You will find below an example showing how to register a serializer/deserializer for ArrayOfArrayOf simple type. My web service doesn't expose such a method, but I tested this code before to publish (with JWSDP 1.3 web service).

Example 1-5. Register complex type "ArrayOfArrayOfint".

I reproduce here the code to register ArrayOfint and ArrayOfArrayOfint, the part concerning ArrayOfArrayOf is in bold. You will be easily able to change it to match another simple type. Do not forget to replace the namespace_to_use characters by a valid namespace.


//register Dynamically int array and arraOfarray type

  QName intArrayTypeQname = new QName( "namespace_to_use" ,
    "ArrayOfint"); // ArrayOfint QName

  QName intArrayElementQname = new QName("", "int"); // int QName

  QName intArrayOfArrayTypeQname = new QName( "namespace_to_use" ,
    "ArrayOfArrayOfint"); // ArrayOfArrayOfint QName

  CombinedSerializer intSerializer = new
    SimpleTypeSerializer(QNAME_TYPE_INT, DONT_ENCODE_TYPE, NULLABLE,
      SOAPConstants.URI_ENCODING, XSDIntEncoder.getInstance());

  CombinedSerializer intArraySerializer = new
    SimpleTypeArraySerializer(intArrayTypeQname, ENCODE_TYPE, NULLABLE,
      SOAPConstants.URI_ENCODING, null, QNAME_TYPE_INT,
      int.class, 1, null, (SimpleTypeSerializer)intSerializer);

  intArraySerializer = new
    ReferenceableSerializerImpl(SERIALIZE_AS_REF,
      intArraySerializer);

  // See below the dimension of the array in italic
  CombinedSerializer intArrayOfArraySerializer = new
    SimpleTypeArraySerializer(intArrayOfArrayTypeQname, ENCODE_TYPE, NULLABLE,
      SOAPConstants.URI_ENCODING, null, QNAME_TYPE_INT,
      int.class, 2, null, (SimpleTypeSerializer)intSerializer);

  intArrayOfArraySerializer = new
    ReferenceableSerializerImpl(SERIALIZE_AS_REF,
      intArrayOfArraySerializer);
                
  SerializerFactory intArraySerializerFactory = new
    SingletonSerializerFactory(intArraySerializer);

  DeserializerFactory intArrayDeserializerFactory = new
    SingletonDeserializerFactory(intArraySerializer);
		  
  SerializerFactory intArrayOfArraySerializerFactory = new
    SingletonSerializerFactory(intArrayOfArraySerializer);

  DeserializerFactory intArrayOfArrayDeserializerFactory = new
    SingletonDeserializerFactory(intArrayOfArraySerializer);


  typeMapping.register(int[].class, intArrayTypeQname,
    intArraySerializerFactory, intArrayDeserializerFactory);

  typeMapping.register(int[][].class, intArrayOfArrayTypeQname,
    intArrayOfArraySerializerFactory, intArrayOfArrayDeserializerFactory);

//end of register int array and arrayofarray type

1.3.5. Client using SEI: conclusion

If you like WSDL2Java you will probably keep the SEI technique for a low-level Java SOAP client definitively. Once the tool is executed, I think this technique is the simplest by allowing a short coding of the client. See Example 1-3, only 7 lines of code for our first ClientSEI class! Not so bad for a low-level API :-). Now if you have to deal with soapAction, see a more classic way of how to use Axis (with a .Net web service), see: the Section called Consume a .NET web service with Java.