You are viewing a plain text version of this content. The canonical link for it is here.
Posted to soap-dev@ws.apache.org by Wouter Cloetens <wc...@raleigh.ibm.com> on 2000/06/26 07:33:22 UTC

Vector and Enumeration implementation (long)

Sorry for the long post. I know I talk too much.

I did an implementation of an encoder that takes a java.util.Vector or 
java.util.Enumeration object and encodes it as a SOAP array. I called it ListSerializer 
("list" as to not confuse Java Enumerations with the SOAP spec's "enumeration" 
nomenclature). Source code included. It still uses com.ibm.soap.

I'd like to see this supported by the base code at some point in time, but for now I'd like to 
discuss a few issues that implementing this raised.

You can't determine the type of the SOAP array like the ArraySerializer can, because 
Vectors and Enumerations have no restrictions on the elements they contain - they're just 
all Objects. ListSerializer has one constructor that takes a Class argument, which defines 
the type. The default, no-argument, constructor will attempt to determine the type by 
cycling through the elements, skipping initial null values (from which you can't determine 
the object type AFAIK), and using the type of the first object as the array type. This 
imposes the following restrictions: the Vector/Enumeration must contain at least one 
non-null element, and all elements must be the same type as that first non-null element.

Now, that's fine on the client side, because you can just instantiate a ListSerializer with its 
Class parameter:
    ListSerializer listSer = new ListSerializer(myType.class);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, arrayQName,
                 java.util.Vector.class, listSer, null);
    smr.mapTypes(Constants.NS_URI_SOAP_ENC, arrayQName,
                 java.util.Enumeration.class, listSer, null);

But on the server side, I can't specify parameters:
[TypeMapping
 encodingStyle=http://schemas.xmlsoap.org/soap/encoding/,
 elementType=http://schemas.xmlsoap.org/soap/encoding/:Array,
 javaType=java.util.Enumeration,
 java2XMLClassName=com.ibm.eservice.encoding.ListSerializer,
 xml2JavaClassName=com.ibm.soap.encoding.soapenc.ArraySerializer]
[TypeMapping
 encodingStyle=http://schemas.xmlsoap.org/soap/encoding/,
 elementType=http://schemas.xmlsoap.org/soap/encoding/:Array,
 javaType=java.util.Vector,
 java2XMLClassName=com.ibm.eservice.encoding.ListSerializer,
 xml2JavaClassName=com.ibm.soap.encoding.soapenc.ArraySerializer]
One solution could be to change the deployment descriptor to make it contain a way to 
pass configurable name-value pairs as initialisation parameters to serialisers and 
deserialisers, similar to servlet init parameters or EJB environment parameters.

Also, it should be possible to specify ListSerializers of different types for different call 
parameters or return values.

Some background on what my target is... In my application, I'm trying to make the SOAP 
interface to EJB access beans, generated by VisualAge for Java, entirely transparant. 
That means I want for instance EJBEnumeration objects to be passed as a 
SOAP-encoded array of the EJBs in the enumeration. I don't want to have to generate a 
different ListSerializer class for every newly deployed EJB method. Maximum 
transparency is a hard to attain goal with SOAP...

Another observation. The array encoder explicitly specifies the type of every element in 
the array. While this makes polymorphism possible (array elements which are of a 
subclass type encoded in a different way), I couldn't find a reference to this behaviour in 
the SOAP spec (then again, it's not always very specific, and I also might have missed it). 
Should we worry about this? Will SOAP implementations in other (perhaps non-OO) 
languages cope with it?
In the case of my ListSerializer, specifying the type of the first non-null object as the array 
type has the inherent risk of that first element being of a subclass type and not the 
common base type for all elements.

Finally, it would be nice to be able to specify that on the target side, the object is not 
serialised as a Java array but rather a Vector (or an Enumeration, or some subclass of 
either). I'm not sure how this can be done transparently.

Altogether, do you guys think encoding these list types as arrays is a good idea, or 
should a new list type be proposed for acceptance in the SOAP spec? One that can deal 
with these issues. Perhaps it also shouldn't require the length of the array to be specified 
up front (more efficient if anyone works out a streaming implementation).

bfn, Wouter

ListSerializer.java: (it's only 5KB so I'm posting it)
package com.ibm.eservice.encoding;

import java.beans.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import org.w3c.dom.*;
import com.ibm.cs.util.*;
import com.ibm.cs.xml.*;
import com.ibm.soap.*;
import com.ibm.soap.rpc.*;
import com.ibm.soap.encoding.soapenc.*;

/**
 * A <code>ListSerializer</code> can be used to serialize
 * java.util.Enumeration and java.util.Vector objects
 * using the <code>SOAP-ENC</code> array encoding style.<br>
 * The array type can be predetermined by passing it to the
 * constructor or using setArrayType().<br>
 * By default, the first non-null element type is assumed to be
 * the array type. In that case, all elements must be of the same
 * type. There must be at least one non-null element.<br>
 * This code is based on <code>ArraySerializer</code>.
 *
 * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
 */
public class ListSerializer implements Serializer
{
  private Class arrayType = null;

  public ListSerializer()
  {
  }

  public ListSerializer(Class arrayType)
  {
    this.arrayType = arrayType;
  }

  public Class getArrayType()
  {
    return arrayType;
  }

  public void setArrayType(Class arrayType)
  {
    this.arrayType = arrayType;
  }


  public void marshall(String inScopeEncStyle, Class javaType, Object srcparam,
                       Object context, Writer sink, NSStack nsStack,
                       XMLJavaMappingRegistry xjmr)
    throws IllegalArgumentException, IOException
  {
    nsStack.pushScope();

    String lengthStr = "";
    Vector src = null;
    Class componentType = arrayType;
    int i, len = 0;
    Object value;

    // First determine the type (Vector or Enumeration) of the array
    // and its length. For an Enumeration, we do this by consuming all
    // the elements and putting them into a Vector.
    if (srcparam != null)
    {
      if (srcparam instanceof Vector)
        src = (Vector)srcparam;
      else if (srcparam instanceof Enumeration)
      {
        src = new Vector();
        Enumeration enum = (Enumeration)srcparam;
        while(enum.hasMoreElements())
          src.addElement(enum.nextElement());
      }

      len = src.size();
      if (len > 0)
        lengthStr = String.valueOf(len);
    }

    // In case the array type was not provided in the constructor,
    // we need to determine it.
    // Enumerations and Vector can contain null elements. We can't determine
    // the type of those. We need to run through the Vector
    // to find the first non-null element.
    if(componentType == null)
    {
      if (len > 0)
        for (i = 0; i < len; i++)
          if ((value = src.elementAt(i)) != null)
          {
            componentType = value.getClass();
            break;
          }
    }

    // If the parameter was null, or not a Vector or Enumeration, and
    // did not contain at least one non-null element, this exception is thrown.
    if (componentType == null)
      throw new IllegalArgumentException("Vector or Enumeration must contain " +
                                         "at least one non-null element.");

    QName elementType = xjmr.queryElementType(componentType,
                                              Constants.NS_URI_SOAP_ENC);

    // If the argument was null or not a Vector or Enumeration,
    // and we know the type, generate a null array.
    if (src == null)
    {
      SoapEncUtils.generateNullArray(inScopeEncStyle,
                                     javaType,
                                     context,
                                     sink,
                                     nsStack,
                                     xjmr,
                                     elementType,
                                     lengthStr);
    }
    else
    {
      SoapEncUtils.generateArrayHeader(inScopeEncStyle,
                                       javaType,
                                       context,
                                       sink,
                                       nsStack,
                                       xjmr,
                                       elementType,
                                       lengthStr);
      sink.write(StringUtils.lineSeparator);
  
      for (i = 0; i < len; i++)
      {
        nsStack.pushScope();
  
        value = src.elementAt(i);
        if (value == null)
        {
          SoapEncUtils.generateNullStructure(inScopeEncStyle, componentType,
                                             "item", sink, nsStack, xjmr);
        }
        else
        {
          Class actualComponentType = value.getClass();
  
          xjmr.marshall(inScopeEncStyle, actualComponentType, value, "item",
                        sink, nsStack);
        }
  
        sink.write(StringUtils.lineSeparator);
        nsStack.popScope();
      }
      sink.write("</" + context + '>');
    }

    nsStack.popScope();
  }
}