Package nexj.core.rpc.xml

Source Code of nexj.core.rpc.xml.XMLMarshaller$ElementListMarshaller

// Copyright 2010 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.rpc.xml;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import nexj.core.meta.Attribute;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Metadata;
import nexj.core.meta.Primitive;
import nexj.core.meta.PrivilegeSet;
import nexj.core.meta.Type;
import nexj.core.persistence.OID;
import nexj.core.rpc.ErrorLocationHolder;
import nexj.core.rpc.MarshallerException;
import nexj.core.rpc.Request;
import nexj.core.rpc.Response;
import nexj.core.rpc.TransferObject;
import nexj.core.rpc.soap.SOAPFault;
import nexj.core.rpc.soap.SOAPMarshaller;
import nexj.core.rpc.soap.SOAPMarshallerException;
import nexj.core.runtime.Context;
import nexj.core.runtime.InvocationContext;
import nexj.core.scripting.PCodeFunction;
import nexj.core.scripting.PCodeMacro;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Symbol;
import nexj.core.util.Base64Util;
import nexj.core.util.Binary;
import nexj.core.util.ErrorCode;
import nexj.core.util.ExceptionHolder;
import nexj.core.util.HashTab;
import nexj.core.util.IdentityHashTab;
import nexj.core.util.Iteratable;
import nexj.core.util.LinkedHashTab;
import nexj.core.util.Lookup;
import nexj.core.util.LookupDeque;
import nexj.core.util.ObjUtil;
import nexj.core.util.PropertyIterator;
import nexj.core.util.Undefined;
import nexj.core.util.WrapperIterator;
import nexj.core.util.XMLWriter;

/**
* Document/literal SOAP/XML marshaller.
* shares most basic objects with SOAPMarshaller hence extends it to use them.
* Needs to extend SOAPMarshaller to gain access to protected marshalling types/objects.
*/
public class XMLMarshaller extends SOAPMarshaller
{
   /**
    * Character marshaller.
    */
   protected final static Marshaller CHAR_MSH = new PrimitiveMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         ((XMLMarshaller)msh).m_writer.writeInt(((Character)obj).charValue());
      }

      public String getType()
      {
         return "unsignedShort";
      }
   };

   /**
    * char[] marshaller.
    */
   protected final static Marshaller CHAR_ARRAY_MSH = new ElementListMarshaller(CHAR_MSH)
   {
      /**
       * @see nexj.core.rpc.rest.XMLMarshaller.ElementListMarshaller
       *      #marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         ((XMLMarshaller)msh).marshalInline(obj, m_sElementNS, m_sElement, m_marshaller, MF_NIL);
      }
   };

   /**
    * Throwable marshaller.
    */
   protected final static Marshaller EXCEPTION_MSH = new ObjectMarshaller()
   {
      public void marshalContents(final Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         Throwable t = (Throwable)obj;
        
         if (obj instanceof ErrorCode)
         {
            ErrorCode e = (ErrorCode)t;
           
            mrsh.marshal(e.getErrorCode(), XML.TNS_NS, "errorCode", STRING_MSH, MF_LITERAL);
            mrsh.marshal((msh.getContext() != null) ? msh.getContext().formatString(e.getErrorCode(), e.getErrorArgs()) :
               t.getLocalizedMessage(), XML.TNS_NS, "errorMessage", STRING_MSH, MF_LITERAL);
            mrsh.marshalInline(e.getErrorArgs(), XML.TNS_NS, "errorArgs", null, MF_NIL | MF_TYPE);
         }
         else
         {
            mrsh.marshal("err." + t.getClass().getName(), XML.TNS_NS, "errorCode", STRING_MSH, MF_LITERAL);
            mrsh.marshal(t.getLocalizedMessage(), XML.TNS_NS, "errorMessage", STRING_MSH, MF_LITERAL);
         }

         if (obj instanceof ErrorLocationHolder)
         {
            final ErrorLocationHolder h = (ErrorLocationHolder)obj;

            mrsh.marshal(h.getClassName(), XML.TNS_NS, "class", STRING_MSH, MF_LITERAL);

            if (h.getOIDHolder() != null)
            {
               mrsh.marshal(h.getOIDHolder().getOID(), XML.TNS_NS, "oid", OID_MSH, MF_LITERAL);
            }

            mrsh.marshal(Primitive.createInteger(h.getOrdinal()), XML.TNS_NS, "ordinal", INTEGER_MSH, 0);

            if (h.getAttributeCount() != 0)
            {
               mrsh.marshalInline(h.getAttributeIterator(), XML.TNS_NS, "attributes", STRING_MSH, MF_LITERAL);
               mrsh.marshalInline(new WrapperIterator(h.getAttributeIterator())
               {
                  public Object next()
                  {
                     return h.findException((String)m_itr.next());
                  }
               }, XML.TNS_NS, "attributeExceptions", EXCEPTION_MSH, MF_LITERAL);
            }
         }
         else
         {
            mrsh.marshal(Primitive.createInteger(-1), null, "ordinal", INTEGER_MSH, 0);
         }

         if (obj instanceof ExceptionHolder)
         {
            if (((ExceptionHolder)obj).getExceptionCount() != 0)
            {
               Iterator exceptionsItr =
                  new WrapperIterator(((ExceptionHolder)obj).getExceptionIterator())
               {
                  private Throwable m_next = null;

                  public boolean hasNext()
                  {
                     for (m_next = (m_next != null || !m_itr.hasNext()) ? m_next : (Throwable)m_itr.next();
                        m_next != null && ObjUtil.isSystem(m_next);
                        m_next = (!m_itr.hasNext()) ? null : (Throwable)m_itr.next());

                     return m_next != null;
                  }

                  public Object next()
                  {
                     hasNext();

                     Object next = m_next;

                     m_next = null;

                     return next;
                  }
               };

               mrsh.marshalInline(exceptionsItr, XML.TNS_NS, "exceptions", EXCEPTION_MSH, MF_LITERAL);
            }
         }
         else
         {
            Throwable cause = t.getCause();

            if (cause != null && ObjUtil.isSystem(cause))
            {
               cause = null;
            }

            mrsh.marshal(cause, XML.TNS_NS, "exceptions", EXCEPTION_MSH, MF_LITERAL);
         }
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Exception";
      }
   };

   /**
    * Function marshaller.
    */
   protected final static Marshaller FUNCTION_MSH = new ObjectMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         PCodeFunction fun = ((PCodeFunction)obj).getCleanCopy();

         mrsh.marshalInline(fun.code, XML.TNS_NS, "code", CHAR_MSH, MF_NIL);
         mrsh.marshalInline(fun.constants, XML.TNS_NS, "constants", null, MF_NIL | MF_TYPE);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Function";
      }
   };

   /**
    * Macro marshaller.
    */
   protected final static Marshaller MACRO_MSH = new ObjectMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         PCodeMacro fun = (PCodeMacro)((PCodeMacro)obj).getCleanCopy();

         mrsh.marshalInline(fun.code, XML.TNS_NS, "code", CHAR_MSH, MF_NIL);
         mrsh.marshalInline(fun.constants, XML.TNS_NS, "constants", null, MF_NIL | MF_TYPE);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Macro";
      }
   };

   /**
    * MS.NET generates it's own type for an object array (called "ArrayOfAnyType"),
    * then puts it into NexJ's namespace.
    * MS.NET can only accept this type for Object[].
    * Our Marshaller therefore defines the type and uses it to marshal Object[].
    * The child tag that MS.NET expects is sometimes "values" and other times it's "item" even
    * though it might be defined opposite in XSD for both "ArrayOfAnyType" and "Array".
    * TODO If anyone can figure why the child tag expected by MS.NET keeps changing then go ahead
    *      and make it pick one or the other, both here and in DLGenerator.
    */
   protected final static Marshaller OBJECT_ARRAY_MSH =
      new ElementListMarshaller(null, "ArrayOfAnyType", XML.TNS_NS, "item");

   /**
    * Object collection marshaller.
    */
   protected final static Marshaller OBJECT_LIST_MSH = new ElementListMarshaller(null);
   /**
    * OID marshaller.
    */
   protected final static Marshaller OID_MSH = new PrimitiveMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         byte[] oid = ((OID)obj).toBinary().getData();

         Binary.write(((XMLMarshaller)msh).m_writer, oid, 0, oid.length);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "OID";
      }
   };

   /**
    * Pair marshaller.
    * Copy from SOAPMarshaller with overridden type.
    */
   protected final static Marshaller PAIR_MSH = new ObjectMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         Pair pair = (Pair)obj;

         mrsh.marshal(pair.getHead(), null, "head", null, MF_TYPE);
         mrsh.marshal(pair.getTail(), null, "tail", null, MF_TYPE);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Pair";
      }
   };

   /**
    * PrivilegeSet marshaller.
    * Copy from SOAPMarshaller with overridden type.
    */
   protected final static Marshaller PRIVILEGE_SET_MSH = new ObjectMarshaller()
   {
      private final Marshaller MASK_MSH = new PrimitiveMarshaller()
      {
         public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
         {
            XMLMarshaller mrsh = (XMLMarshaller)msh;

            Base64Util.encode(new ByteArrayInputStream(((PrivilegeSet)obj).getMask()), mrsh.m_writer, -1, false);
         }

         public String getType()
         {
            return "base64Binary";
         }
      };

      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;

         mrsh.marshal(obj, null, "mask", MASK_MSH, MF_LITERAL);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "PrivilegeSet";
      }
   };

   /**
    * byte[] marshaller.
    * Copy from SOAPMarshaller with overridden type.
    */
   protected final static Marshaller BYTE_VECTOR_MSH = new ObjectMarshaller()
   {
      private final Marshaller MASK_MSH = new PrimitiveMarshaller()
      {
         public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
         {
            XMLMarshaller mrsh = (XMLMarshaller)msh;

            Base64Util.encode(new ByteArrayInputStream((byte[])obj), mrsh.m_writer, -1, false);
         }

         public String getType()
         {
            return "base64Binary";
         }
      };

      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;

         mrsh.marshal(obj, null, "value", MASK_MSH, MF_LITERAL);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "ByteVector";
      }
   };

   /**
    * SOAP Fault marshaller.
    */
   protected final static Marshaller SOAP_FAULT_MSH = new ObjectMarshaller()
   {
      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.ObjectMarshaller
       *      #marshalAttributes(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalAttributes(Object obj, SOAPMarshaller msh) throws IOException
      {
          // add namespace for SOAP Fault
         ((XMLMarshaller)msh).writeAttribute("xmlns", XML.ENV_NS, null, XML.ENV_URI, null);
      }

      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         SOAPFault fault = (SOAPFault)obj;
        
         if (fault.getCode() != null)
         {
            mrsh.marshal(((XML.ENV_URI.equals(fault.getURI())) ? XML.ENV_NS + ":" : "") + fault.getCode(),
               XML.ENV_NS, "faultcode", STRING_MSH, MF_LITERAL);
         }

         if (fault.getMessage() != null)
         {
            mrsh.marshal(fault.getMessage(), XML.ENV_NS, "faultstring", STRING_MSH, MF_LITERAL);
         }
        
         if (fault.getException() != null)
         {
            mrsh.marshal(fault.getException(), XML.ENV_NS, "detail", EXCEPTION_MSH, MF_LITERAL);
         }
      }

      public String getNamespace()
      {
         return XML.ENV_NS;
      }

      public String getType()
      {
         return "Fault";
      }
   };

   /**
    * Symbol marshaller.
    * Copy from SOAPMarshaller with overridden type.
    */
   protected final static Marshaller SYMBOL_MSH = new ObjectMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;

         mrsh.marshal(((Symbol)obj).getName(), null, "name", STRING_MSH, MF_LITERAL);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Symbol";
      }
   };

   /**
    * XMLElement marshaller.
    */
   protected final static Marshaller XML_ELEMENT_MSH = new ElementMarshaller(null, null)
   {
      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementMarshaller#getElementType(java.lang.Object)
       */
      protected QName getElementType(Object obj)
      {
         return ((XMLElement)obj).getElement();
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.ObjectMarshaller#marshalAttributes(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalAttributes(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         XMLElement element = (XMLElement)obj;

         obj = element.getObject();

         if (obj == null)
         {
            mrsh.writeAttribute(XML.XSI_NS, "nil", true); // required if XMLElement holds a null

            return;
         }

         Marshaller marshaller = mrsh.findMarshaller(obj, element.getType()); // suppress xsi:type

         if (marshaller == null)
         {
            marshaller = mrsh.findMarshaller(obj);
            mrsh.writeAttribute(
               XML.XSI_NS, "type", marshaller.getNamespace(), marshaller.getType(), null);
         }

         marshaller.marshalAttributes(obj, msh);
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementMarshaller#marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         if (obj != null) // omit body if XMLElement holds a null
         {
            super.marshalContents(((XMLElement)obj).getObject(), msh);
         }
      }
   };

   /**
    * XMLElementList marshaller.
    */
   protected final static Marshaller XML_ELEMENT_LIST_MSH = new ElementListMarshaller(null)
   {
      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementListMarshaller#getElement(java.lang.Object)
       */
      public String getElement(Object obj)
      {
         return ((XMLElementList)obj).getElement().getLocalPart();
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementListMarshaller#getNamespace(java.lang.Object)
       */
      public String getNamespace(Object obj)
      {
         return ((XMLElementList)obj).getElement().getNamespaceURI();
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementListMarshaller#marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         XMLElementList element = (XMLElementList)obj;

         obj = element.getObject();

         String sPrefix = mrsh.getNamespacePrefix(element.getElement().getNamespaceURI());
         Marshaller marshaller = mrsh.findMarshaller(obj, element.getType()); // suppress xsi:type

         mrsh.marshalInline(obj, sPrefix, m_sElement, marshaller, MF_NIL);
      }
   };

   /**
    * XMLCollection marshaller with support for type restriction.
    * i.e. Marshaller for metaclass collections that are restricted to a certain type
    * (i.e. don't support polymorhism).
    */
   protected final static Marshaller XML_RESTRICTED_COLLECTION_MSH = new ElementListMarshaller(null)
   {
      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementListMarshaller#getElement(java.lang.Object)
       */
      public String getElement(Object obj)
      {
         XMLCollection response = (XMLCollection)obj;
         String sElement = XML.BASE_PREFIX;

         if (response.getMetaclass() != null)
         {
            sElement += response.getMetaclass().getName() + '-';
         }

         sElement += (response.getType() == null) ? "Collection" : response.getType();

         return sElement;
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.ElementListMarshaller#marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         if (obj == null)
         {
            return;
         }

         XMLMarshaller mrsh = (XMLMarshaller)msh;
         XMLCollection collection = (XMLCollection)obj;
         Metaclass restriction = collection.getMetaclass();
         Iterator iterator = collection.getCollection().iterator();

         // marshal as an unrestricted list
         if (restriction == null)
         {
            mrsh.marshalInline(iterator, m_sElementNS, m_sElement, null, MF_NIL);

            return;
         }

         boolean bDynamicTypeEnabled = mrsh.m_bDynamicTypeEnabled; // remember original value

         mrsh.m_bDynamicTypeEnabled = false;

         // go through all elements and see if can marshal them as the restricted metatype
         // (or null otherwise)
         while (iterator.hasNext())
         {
            Object tmpObj = iterator.next();
            String sClassName = (tmpObj instanceof TransferObject)
                              ? ((TransferObject)tmpObj).getClassName() : null;

            // Check if 'meta' derives from 'restriction'
            if (sClassName == null ||
                !restriction.isUpcast(mrsh.m_metadata.findMetaclass(sClassName)))
            {
               mrsh.marshal(null, m_sElementNS, m_sElement, null, MF_NIL); // not a supported class
            }
            else // select a marshaller with the 'restriction' in mind i.e. at most 'restriction'
            {
               Marshaller marshaller = mrsh.findMetaclassMarshaller(restriction.getName());

               mrsh.marshal(tmpObj, m_sElementNS, m_sElement, marshaller, MF_NIL);
            }
         }

         mrsh.m_bDynamicTypeEnabled = bDynamicTypeEnabled;
      }
   };

   /**
    * TransferObject marshaller.
    * The value for bDynamicMetaclassTypes doesn't matter since TransferObject has no attributes.
    */
   protected final static Marshaller TRANSFER_OBJECT_MSH = new MetaclassMarshaller(null, true);

   // declare after the rest as it needs TRANSFER_OBJECT_UNMSH declared

   /**
    * Request marshaller used by jUnit tests.
    */
   protected final static Marshaller REQUEST_MSH = new ObjectMarshaller()
   {
      // Request.Invocation marshaller
      private final Marshaller INVOCATION_MSH = new ObjectMarshaller()
      {
         public String getType()
         {
            return XML.BASE_PREFIX + "Invocation";
         }

         public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
         {
            XMLMarshaller mrsh = (XMLMarshaller)msh;
            Request.Invocation invocation = (Request.Invocation)obj;

            mrsh.marshal(invocation.getObject(), XML.TNS_NS, "object", null, MF_NIL);
            mrsh.marshal(invocation.getEventName(), XML.TNS_NS, "event", STRING_MSH, 0);
            mrsh.marshalInline(invocation.getArguments(), XML.TNS_NS, "arg", null, MF_NIL);
            mrsh.marshal(invocation.getAttributes(), XML.TNS_NS, "attributes", PAIR_MSH, 0);
         }
      };

      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         final Request req = (Request)obj;

         mrsh.marshal(req.getNamespace(), XML.TNS_NS, "namespace", STRING_MSH, MF_LITERAL);
         mrsh.marshal(req.getVersion(), XML.TNS_NS, "version", STRING_MSH, MF_LITERAL);
         mrsh.marshal(Boolean.valueOf(req.isAsync()), XML.TNS_NS, "async", BOOLEAN_MSH, MF_LITERAL);
         mrsh.marshal(Boolean.valueOf(req.isCommit()), XML.TNS_NS, "commit", BOOLEAN_MSH, MF_LITERAL);
         mrsh.marshal(req.getLocale(), XML.TNS_NS, "locale", LOCALE_MSH, MF_LITERAL);
         mrsh.marshal(req.getTimeZone(), XML.TNS_NS, "timeZone", TIME_ZONE_MSH, MF_LITERAL);
         mrsh.marshal(req.getCorrelator(), XML.TNS_NS, "correlator", TRANSFER_OBJECT_MSH, 0);
         mrsh.marshalInline(
            req.getInvocationIterator(), XML.TNS_NS, "invocation", INVOCATION_MSH, MF_NIL);
         mrsh.marshalInline(req.getFilterIterator(), XML.TNS_NS, "filters", TRANSFER_OBJECT_MSH, MF_NIL);
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Request";
      }
   };

   /**
    * Response marshaller.
    */
   protected final static Marshaller RESPONSE_MSH = new ObjectMarshaller()
   {
      protected final Marshaller EVENTS_MSH = new ElementListMarshaller(
            new ElementListMarshaller(null, XML.TNS_NS, "item"), XML.TNS_NS, "item");

      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         Response resp = (Response)obj;

         mrsh.marshalInline(resp.getResultIterator(), XML.TNS_NS, "results", null, MF_NIL);

         if (resp.getEventCount() != 0)
         {
            mrsh.marshal(resp.getEventIterator(), XML.TNS_NS, "events", EVENTS_MSH, MF_NIL);
         }
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Response";
      }
   };

   /**
    * Null response marshaller.
    */
   protected final static Marshaller NIL_MSH = new ObjectMarshaller()
   {
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
      }

      public String getType()
      {
         return XML.BASE_PREFIX + "Nil";
      }
   };

   /**
    * Map of class objects to marshallers: Marshaller[Class].
    */
   protected final static Lookup s_marshallerMap = new HashTab(64);

   static
   {
      // can't use OBJECT_LIST_MSH because MS.NET can only unmarshal lists as Object[],
      // it will fail to unmarshal any type other than "ArrayOfAnyType"
      s_marshallerMap.put(ArrayList.class, OBJECT_ARRAY_MSH);
      s_marshallerMap.put(char[].class, CHAR_ARRAY_MSH);
      s_marshallerMap.put(Object[].class, OBJECT_ARRAY_MSH);
      s_marshallerMap.put(OID.class, OID_MSH);
      s_marshallerMap.put(Pair.class, PAIR_MSH);
      s_marshallerMap.put(PCodeFunction.class, FUNCTION_MSH);
      s_marshallerMap.put(PCodeMacro.class, MACRO_MSH);
      s_marshallerMap.put(PrivilegeSet.class, PRIVILEGE_SET_MSH);
      s_marshallerMap.put(byte[].class, BYTE_VECTOR_MSH);
      s_marshallerMap.put(Request.class, REQUEST_MSH);
      s_marshallerMap.put(Response.class, RESPONSE_MSH);
      s_marshallerMap.put(SOAPFault.class, SOAP_FAULT_MSH);
      s_marshallerMap.put(Symbol.class, SYMBOL_MSH);
      s_marshallerMap.put(String[].class, new ElementListMarshaller(STRING_MSH));
      s_marshallerMap.put(TransferObject.class, TRANSFER_OBJECT_MSH);
      s_marshallerMap.put(XMLCollection.class, XML_RESTRICTED_COLLECTION_MSH);
      s_marshallerMap.put(XMLElement.class, XML_ELEMENT_MSH);
      s_marshallerMap.put(XMLElementList.class, XML_ELEMENT_LIST_MSH);
   }

   // attributes

   /**
    * ID counter for untyped marshalled objects.
    */
   protected int m_nNextMarshalledObjectId;

   /**
    * If dynamic marshaller type detection should be enabled or STRING_MSH used instead.
    */
   protected boolean m_bDynamicTypeEnabled;

   /**
    * Preserve element namespace prefix definitions after close of element, to be reused by next
    * element.
    */
   protected boolean m_bPreserveNS;

    /**
    * The default namespace URI for current XML element level.
    */
   protected String m_sDefaultNS;

   /**
    * Map containing currently defined namespace prefix for a given namespace URI key.
    * Default map size of 4 == number of elements added in serialize(...).
    * Any key instanceof String is a namespace URI to namespace prefix mapping.
    * Any key instanceof Integer is delimiter and contains default namespace defined for parent.
    * Newest definitions always at the front since no reverseIterator available for LookupDeque.
    * Upon openElement all namespaces from start of map up to the first Integer key will be defined.
    * Upon closeElement a new Integer will be added with the default namespace for this element.
    * Upon closeEmptyElement/endElement all namespaces from start of map up to but excluding the
    * second Integer key will be removed, with the value of the first Integer key set as default
    * namespace.
    * e.g. post openElement {0->"", URI0->"ns", 1->URI0, URI1->"", URI2->"q0" ... URIx->"qY"}
    * e.g. post closeElement/endElement
    *      {0->"", URI0->"ns", 1->URI0, URI1->"", URI2->"q0" ... URIj->"qK", N->URI1}
    * Note: Redefining default namespace between openElement and closeElement may cause the current
    *       element prefix to be invalid in the generated XML.
    */
   protected LookupDeque/*<Object, String>*/ m_nsMap = new LinkedHashTab/*<Object, String>*/(4);

   // associations

   /**
    * Map of Object (Metaclass) name Marshaller (String->Marshaller).
    * Used to store metadata marshallers instantiated based on current invocation context.
    */
   protected final Lookup/*<String, Marshaller>*/ m_marshallerMap = new HashTab();
  
   /**
    * The metadata collection to query for descriptions.
    */
   protected Metadata m_metadata;

   // constructors

   /**
    * Constructs the marshaller.
    * @param context The invocation context.
    * @param metadata The root metadata object for type information.
    */
   public XMLMarshaller(Context context, Metadata metadata)
   {
      super(context);
      m_metadata = metadata;
   }

   /**
    * Constructs the marshaller.
    * @param context The invocation context.
    */
   public XMLMarshaller(InvocationContext context)
   {
      this(context, context.getMetadata());
   }

   /**
    * Constructs the marshaller.
    * @param context The invocation context.
    */
   public XMLMarshaller(Context context)
   {
      this((InvocationContext)context);
   }

   // operations

   /**
    * Close an XML element header tag without closing the actual XML element.
    * @throws IOException  On output error.
    */
   protected void closeElement() throws IOException
   {
      m_nsMap.putFirst(Primitive.createInteger(m_nsMap.size()), m_sDefaultNS);
      m_writer.closeElement();
   }

   /**
    * Close an XML element without a defined body.
    * @throws IOException On output error.
    */
   protected void closeEmptyElement() throws IOException
   {
      popNamespacePrefix();
      m_writer.closeEmptyElement();
   }

   /**
    * End an XML element.
    * @param sPrefix The element prefix.
    * @param sElement The name of the element.
    * @throws IOException On output error.
    */
   protected void endElement(String sPrefix, String sElement) throws IOException
   {
      // close of parent element after NS preserve specified in child
      if (m_nsMap.firstKey() instanceof String)
      {
         boolean bPreserveNS = m_bPreserveNS;

         bPreserveNS = false;
         popNamespacePrefix();
         m_bPreserveNS = bPreserveNS;
      }

      m_nsMap.removeFirst(); // first element is delimiter for current
      popNamespacePrefix();

      //second condition is SOAP backwards compatibility kluge since SOAP assumes default XML.TNS_NS
      if (sPrefix == XMLConstants.DEFAULT_NS_PREFIX || sPrefix == XML.TNS_NS)
      {
         sPrefix = null;
      }

      m_writer.setNamespace(sPrefix);
         m_writer.endElement(sElement);
      m_writer.setNamespace(null);
   }

   /**
    * Gets a marshaller for an object.
    * @param obj The object for which to get the marshaller. Cannot be null.
    * @return The marshaller to use for the specified object.
    * @throws SOAPMarshallerException If a marshaller cannot be found.
    */
   protected Marshaller findMarshaller(Object obj) throws SOAPMarshallerException
   {
      Marshaller msh = null;

      if (obj instanceof TransferObject)
      {
         msh = findMetaclassMarshaller(((TransferObject)obj).getClassName());
      }

      return (msh != null) ? msh : XMLMarshaller.getMarshaller(obj);
   }

   /**
    * Get the marshaller for the object only if the object is of the expected type.
    * @param obj The object for which to get the marshaller.
    * @param type The expected object type to get the marshaller for.
    * @return The marshaller for the type or null if type does not match object's type.
    * @throws SOAPMarshallerException If a marshaller cannot be found for type.
    */
   protected Marshaller findMarshaller(Object obj, Type type)
   {
      // expected type equal to type of obj
      if ((type instanceof Primitive && type != Primitive.ANY && type == Primitive.typeOf(obj)) ||
          (type instanceof Metaclass &&
           obj instanceof TransferObject &&
           type.getName().equals(((TransferObject)obj).getClassName())))
      {
         return findMarshaller(obj);
      }

      return null;
   }

   /**
    * Get a marshaller for a specific metaclass
    * @param sType The type of the marshaller to get. Can be null.
    * @return The marshaller or null if not found
    */
   protected Marshaller findMetaclassMarshaller(String sType)
   {
      // check with locally registered marshallers (invocation context sensitive)
      Marshaller msh = (sType == null) ? null : (Marshaller)m_marshallerMap.get(sType);
     
      if (msh != null || sType == null) // getMetaclass() does not like null type
      {
         return msh;
      }

      Metaclass metaclass = m_metadata.findMetaclass(sType);

      if (metaclass != null)
      {
         msh = new MetaclassMarshaller(metaclass, m_bDynamicTypeEnabled);
         m_marshallerMap.put(sType, msh); // put it in map so can reuse same unmarshaller later
      }

      return msh;
   }

   /**
    * Gets a marshaller for an object.
    * @param obj The object for which to get the marshaller. Cannot be null.
    * @return The marshaller to use for the specified object.
    * @throws SOAPMarshallerException If a marshaller cannot be found.
    */
   protected static Marshaller getMarshaller(Object obj) throws SOAPMarshallerException
   {
      Marshaller msh = (obj == null) ? null : (Marshaller)s_marshallerMap.get(obj.getClass());

      if (msh == null && obj instanceof Throwable)
      {
         return EXCEPTION_MSH;
      }

      return (msh != null) ? msh : SOAPMarshaller.getMarshaller(obj);
   }

   /**
    * Gets a marshaller for a specific java.lang.Class.
    * @param type The class to retrieve the object for.
    * @return The marshaller to use for a specific object or null if not found.
    */
   protected static Marshaller getMarshallerByClass(Class type)
   {
      // Lookup cannot handle null keys
      Marshaller msh = (type == null) ? null : (Marshaller)s_marshallerMap.get(type);

      if (msh == null && Throwable.class.isAssignableFrom(type))
      {
         return EXCEPTION_MSH;
      }

      // check with parent class
      return (msh != null) ? msh : (Marshaller)SOAPMarshaller.s_mshMap.get(type);
   }

   /**
    * Generate or return unique prefix for an XML namespace.
    * @param sURI The XML namespace URI (not null).
    * @return Prefix that will be valid for the URI on next openElement(String, String) call.
    */
   protected String getNamespacePrefix(String sURI)
   {
      assert sURI != null;

      if (sURI == m_sDefaultNS || sURI.equals(m_sDefaultNS)) // first condition is optimization
      {
         return XMLConstants.DEFAULT_NS_PREFIX;
      }

      String sPrefix = (String)m_nsMap.get(sURI);

      if (sPrefix == null)
      {
         // use m_nsMap.size() as reasonable approximation of upper bound of possible NS definitions
         sPrefix = "q" + m_nsMap.size(); // 'q' for qualified type/name
         m_nsMap.putFirst(sURI, sPrefix);
      }

      return sPrefix;
   }

   /**
    * Generate or return unique ID for an object
    * (same object will always get same ID no matter how many times it's asked for).
    * @param obj The object for which to find the ID.
    * @return ID for the object.
    */
   protected String getObjectId(Object obj)
   {
      return (String)m_idMap.get(obj);
   }

   /**
    * @see nexj.core.rpc.soap.SOAPMarshaller#marshal(char, java.lang.String, java.lang.String)
    */
   protected void marshal(char ch, String sNamespace, String sElement) throws IOException
   {
      startElement(sNamespace, sElement);
         m_writer.writeInt(ch);
      endElement(sNamespace, sElement);
   }

   /**
    * @see nexj.core.rpc.soap.SOAPMarshaller#marshal(int, java.lang.String, java.lang.String)
    */
   protected void marshal(int n, String sNamespace, String sElement) throws IOException
   {
      startElement(sNamespace, sElement);
         m_writer.writeInt(n);
      endElement(sNamespace, sElement);
   }

   /**
    * @see nexj.core.rpc.soap.SOAPMarshaller#marshal(long, java.lang.String, java.lang.String)
    */
   protected void marshal(long l, String sNamespace, String sElement) throws IOException
   {
      startElement(sNamespace, sElement);
         m_writer.write(Long.toString(l));
      endElement(sNamespace, sElement);
   }

   /**
    * Marshals an object as xsd:anyType.
    * @param obj The object to marshal. Can be null.
    * @param sNamespace The XML element namespace name (not URI).
    * @param sElement The XML element name.
    * @param msh The marshaller to use, or null to determine automatically.
    * @param nFlags Combination of MF_* constants.
    */
   protected void marshal(Object obj, String sNamespace, String sElement, Marshaller msh, int nFlags) throws IOException
   {
      if (obj == null)
      {
         if ((nFlags & MF_NIL) != 0)
         {
            openElement(sNamespace, sElement);
               writeAttribute(XML.XSI_NS, "nil", true);
            closeEmptyElement();
         }

         return;
      }

      // force String type when marshalling without dynamic marshaller detection
      // (no need to output type because this is explicitly expected)
      if (msh == null && !m_bDynamicTypeEnabled)
      { 
         msh = STRING_MSH;
         obj = obj.toString();
      }

      Marshaller marshaller = (msh != null) ? msh : findMarshaller(obj); // actual Marshaller to use

      // non-primitive marshallers should not output same objects
      // primitive marshallers do some strange things that cause this algorithm to break anyways
      // e.g. PrimitiveSet->mask
      if (!marshaller.isPrimitive())
      {
         String sId = getObjectId(obj);

         // don't output object again if it was already output once
         if (sId != null)
         {
            if (marshaller instanceof MetaclassMarshaller)
            {
               // MetaclassMarshallers know how to do shallow copies of themselves
               marshaller = ((MetaclassMarshaller)marshaller).REF_MARSHALLER;
            }
            else
            {
               IOException e = new IOException("Circular references are not supported");

               e.initCause(new SOAPMarshallerException("err.rpc.xml.circularReference",
                                                       new Object[]{obj}));

               throw e;
            }
         }
      }

      String sId = null;

      openElement(sNamespace, sElement);

         // force type specification if a dynamic lookup was done for a non-collection
         if ((nFlags & MF_TYPE) != 0 || msh == null)
         {
            writeAttribute(XML.XSI_NS, "type", marshaller.getNamespace(), marshaller.getType(), null);
         }

         // mark/track untyped objects with a unique ID
         if (!marshaller.isPrimitive())
         {
            sId = toId(m_nNextMarshalledObjectId++);
            m_idMap.put(obj, sId);
         }

         marshaller.marshalAttributes(obj, this);
      closeElement();
         marshaller.marshalContents(obj, this);
      endElement(sNamespace, sElement);

      // remove object from recursion tracking as we're done with this branch
      // (used to leave one object copy per branch)
      if (sId != null)
      {
         m_idMap.remove(obj);
      }
   }

   /**
    * If value is actually a collection of individual values then marshal each individual
    * separately, otherwise marshal as a single object.
    * @param obj The object to marshal.
    * @param sPrefix The prefix of the element to marshal into (not null).
    * @param sElement The element name to marshal into (not null).
    * @param marshaller The marshaller to marshal the object with (null == dynamic).
    * @param nFlags Combination of MF_* constants to set marshaling options.
    * @throws IOException On output IO error.
    */
   protected void marshalInline(
      Object obj, String sPrefix, String sElement, Marshaller marshaller, int nFlags)
      throws IOException
   {
      if (obj == null)
      {
         marshal(obj, sPrefix, sElement, marshaller, MF_NIL);

         return;
      }

      if (obj instanceof Iterable)
      {
         obj = ((Iterable)obj).iterator();
      }
      else if (obj instanceof Iteratable)
      {
         obj = ((Iteratable)obj).iterator();
      }

      boolean bPreserveNS = m_bPreserveNS;

      m_bPreserveNS = true;

      if (obj instanceof Iterator)
      {
         for (Iterator itr = (Iterator)obj; itr.hasNext();)
         {
            marshal(itr.next(), sPrefix, sElement, marshaller, nFlags);
         }
      }
      else if (obj instanceof Object[])
      {
         Object[] array = (Object[])obj;

         for (int i = 0; i < array.length; ++i)
         {
            marshal(array[i], sPrefix, sElement, marshaller, nFlags);
         }
      }
      else if (obj instanceof char[])
      {
         char[] chArray = (char[])obj;

         for (int i = 0; i < chArray.length; ++i)
         {
            marshal(chArray[i], sPrefix, sElement);
         }
      }
      else
      {
         marshal(obj, sPrefix, sElement, marshaller, nFlags);
      }

      m_bPreserveNS = bPreserveNS;
      popNamespacePrefix();
   }

   /**
    * Open an XML element and output initial attributes.
    * @param sPrefix The element prefix.
    * @param sElement The name of the element.
    * @throws IOException On output error.
    */
   protected void openElement(String sPrefix, String sElement) throws IOException
   {
      //second condition is SOAP backwards compatibility kluge since SOAP assumes default XML.TNS_NS
      if (sPrefix == XMLConstants.DEFAULT_NS_PREFIX || sPrefix == XML.TNS_NS)
      {
         sPrefix = null;
      }

      m_writer.setNamespace(sPrefix);
         m_writer.openElement(sElement);
      m_writer.setNamespace(null);

      String sDefaultNS;

      if (m_nsMap.firstKey() instanceof String) // optimization to avoid creating Iterator
      {
         m_writer.setNamespace(XML.XML_NS);

            Lookup.Iterator/*<Object, String>*/ itr = m_nsMap.iterator();

            while (itr.hasNext() && itr.next() instanceof String)
            {
               m_writer.writeAttribute((String)itr.getValue(), (String)itr.getKey());
            }

         m_writer.setNamespace(null);

         sDefaultNS = (String)itr.getValue();
      }
      else
      {
         sDefaultNS = (String)m_nsMap.firstValue();
      }

      if (sDefaultNS != m_sDefaultNS) // default namespace has changed from last tag
      {
         m_writer.writeAttribute(XML.XML_NS, m_sDefaultNS); // output default namespace
      }
   }

   /**
    * Remove all namespace prefixes defined for the current element level.
    */
   protected void popNamespacePrefix()
   {
      if (!m_bPreserveNS)
      {
         while(m_nsMap.firstKey() instanceof String) // delimiter for current not in map
         {
            m_nsMap.removeFirst();
         }

         m_sDefaultNS = (String)m_nsMap.firstValue();
      }
   }

   /**
    * Start an XML element and output initial attributes.
    * @param sPrefix The element prefix.
    * @param sElement The name of the element.
    * @throws IOException On output error.
    */
   protected void startElement(String sPrefix, String sElement) throws IOException
   {
      openElement(sPrefix, sElement);
      closeElement();
   }

   /**
    * @see nexj.core.rpc.CharacterStreamMarshaller#serialize(java.lang.Object, java.io.Writer)
    */
   public void serialize(Object obj, Writer writer) throws IOException, MarshallerException
   {
      m_writer = (writer instanceof XMLWriter) ? (XMLWriter)writer : new XMLWriter(writer);
      m_idMap = new IdentityHashTab();
      m_nNextMarshalledObjectId = 0;
      m_bDynamicTypeEnabled = true;
      m_sDefaultNS = XML.NS_URI_TNS;

      // load all header attributes for first element
      m_nsMap.putFirst(Primitive.createInteger(m_nsMap.size()), XMLConstants.NULL_NS_URI);
      m_nsMap.putFirst(XML.XSD_URI, XML.XSD_NS);
      m_nsMap.putFirst(XML.XSI_URI, XML.XSI_NS);
      m_nsMap.putFirst(XML.NS_URI_TNS, XML.TNS_NS);

      int nFlags = MF_LITERAL;
      Marshaller msh;

      if (obj == null)
      {
         msh = NIL_MSH;
         nFlags |= MF_NIL;
      }
      else
      {
         msh = findMarshaller(obj);
      }

      String sPrefix;
      String sElement;

      if (msh instanceof TypedElementMarshaller)
      {
         TypedElementMarshaller mrsh = (TypedElementMarshaller)msh;

         sPrefix = getNamespacePrefix(mrsh.getNamespace(obj));
         sElement = mrsh.getElement(obj);
      }
      else
      {
         sPrefix = msh.getNamespace();
         sElement = msh.getType();
      }

      marshal(obj, sPrefix, sElement, msh, nFlags);
   }

   /**
    * Output an attribute.
    * @param sPrefix The element prefix.
    * @param sName The element name.
    * @param bValue The value.
    * @throws IOException On output error.
    */
   protected void writeAttribute(String sPrefix, String sName, boolean bValue) throws IOException
   {
      m_writer.setNamespace((sPrefix != null && sPrefix != XML.TNS_NS) ? sPrefix : null);
         m_writer.writeAttribute(sName, bValue);
      m_writer.setNamespace(null);
   }

   /**
    * Output an attribute.
    * @param sPrefix The element prefix.
    * @param sName The element name.
    * @param sValuePrefix The value prefix.
    * @param sValue0 The first part of the value (null == skip).
    * @param sValue1 The second part of the value (null == skip).
    * @throws IOException On output error.
    */
   protected void writeAttribute(String sPrefix, String sName, String sValuePrefix, String sValue0, String sValue1)
      throws IOException
   {
      m_writer.setNamespace((sPrefix != null && sPrefix != XML.TNS_NS) ? sPrefix : null);

         if (sValuePrefix != null && sValuePrefix != XML.TNS_NS)
         {
            m_writer.writeAttribute(sName, sValuePrefix, ":", sValue0, sValue1);
         }
         else
         {
            m_writer.writeAttribute(sName, sValue0, sValue1);
         }

      m_writer.setNamespace(null);
   }

   // inner classes

   /**
    * Marshaller for lists producing a "values" tag for each element.
    * The object to marshal must either be an Iterator, Iterable or Object[].
    * This class will not work from primitive arrays
    * (override marshalContents(Object, SOAPMarshaller) for that).
    */
   protected static class ElementListMarshaller
      extends ObjectMarshaller implements TypedElementMarshaller
   {
      // attributes

      /**
       * The collection type to use.
       */
      protected String m_sType;

      /**
       * The tag namespace to use for sub elements.
       */
      protected String m_sElementNS;
     
      /**
       * The tag to use for sub elements.
       */
      protected String m_sElement;

      // associations

      /**
       * The marshaller to use for individual elements.
       */
      protected Marshaller m_marshaller;

      // constructors
     
      /**
       * Constructor.
       * @param marshaller The marshaller to use (null means dynamic).
       * @param sType The marshaller type name (null for default).
       * @param sElementNS The namespace (tag not URI) of sElement to use
       *                   (if sElement then default will be used).
       * @param sElement The tag to use for sub elements (null for default).
       */
      public ElementListMarshaller(Marshaller marshaller, String sType, String sElementNS, String sElement)
      {        
         m_marshaller = marshaller;

         if (sType == null)
         {
            if (marshaller != null)
            {
               sType = marshaller.getType() + XML.ARRAY_SUFFIX;
            }
            else
            {
               sType = XML.BASE_PREFIX + "Collection";
            }
         }

         m_sType = sType;
         m_sElementNS = (sElementNS == null) ?  XML.TNS_NS : sElementNS;
         m_sElement = (sElement == null) ? "item" : sElement;
      }

      /**
       * Constructor.
       * @param marshaller The marshaller to use (null means dynamic).
       * @param sElementNS The namespace (tag not URI) of sElement to use
       *                   (if sElement then default will be used).
       * @param sElement The tag to use for sub elements (null for default).
       */
      public ElementListMarshaller(Marshaller marshaller, String sElementNS, String sElement)
      {
         this(marshaller, null, sElementNS, sElement);
      }

      /**
       * Convenience constructor providing default values.
       * @param marshaller The marshaller to use (null means dynamic).
       */
      public ElementListMarshaller(Marshaller marshaller)
      {
         this(marshaller, null, null, null);
      }

      // operations

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.TypedElementMarshaller#getElement(java.lang.Object)
       */
      public String getElement(Object obj)
      {
         return getType();
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.TypedElementMarshaller#getNamespace(java.lang.Object)
       */
      public String getNamespace(Object obj)
      {
         return XML.NS_URI_TNS;
      }
      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller#getType()
       */
      public String getType()
      {
         return m_sType;
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller
       *      #marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         ((XMLMarshaller)msh).marshalInline(obj, m_sElementNS, m_sElement, m_marshaller, MF_NIL);
      }
   }

   /**
    * Marshaller for individual objects.
    */
   protected static class ElementMarshaller
      extends ObjectMarshaller implements TypedElementMarshaller
   {
      /**
       * Default element type.
       */
      protected static QName s_defaultType = WADLGenerator.computeElementType(Primitive.ANY, false);

      /**
       * The marshaller to use for individual elements.
       */
      protected Marshaller m_marshaller;

      /**
       * The element/tag type to use.
       */
      protected QName m_type;

      /**
       * Constructor.
       * @param marshaller The marshaller to use (null means dynamic).
       * @param type The marshaller element/tag type (null for dynamic).
       */
      public ElementMarshaller(Marshaller marshaller, QName type)
      {
         m_marshaller = marshaller;
         m_type = type;
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.TypedElementMarshaller#getElement(java.lang.Object)
       */
      public String getElement(Object obj)
      {
         return getElementType(obj).getLocalPart();
      }

      /**
       * @see nexj.core.rpc.xml.XMLMarshaller.TypedElementMarshaller#getNamespace(java.lang.Object)
       */
      public String getNamespace(Object obj)
      {
         return getElementType(obj).getNamespaceURI();
      }

      /**
       * Return the qualified type of the element representing the marshalled object.
       * @param The object to be marshalled that requires a namespace URI.
       * @return The element/tag qualified type to use when marshalling this object.
       */
      protected QName getElementType(Object obj)
      {
         if (m_type != null)
         {
            return m_type;
         }

         return (obj instanceof Type)
                ? WADLGenerator.computeElementType((Type)obj, false) : s_defaultType;
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller#getType()
       */
      public String getType()
      {
         return getElement(null);
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.ObjectMarshaller#marshalAttributes(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalAttributes(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         Marshaller marshaller = m_marshaller;

         if (marshaller == null)
         {
            marshaller = mrsh.findMarshaller(obj);
            mrsh.writeAttribute(
               XML.XSI_NS, "type", marshaller.getNamespace(), marshaller.getType(), null);
         }

         marshaller.marshalAttributes(obj, msh);
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller#marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         Marshaller marshaller = (m_marshaller == null) ? mrsh.findMarshaller(obj) : m_marshaller;

         marshaller.marshalContents(obj, mrsh);
      }
   }

   /**
    * Class to marshal TransferObjects into metaclass instances.
    */
   protected static class MetaclassMarshaller extends ObjectMarshaller
   {
      /**
       * The type of this object.
       */
      protected String m_sType = XML.BASE_PREFIX + "TransferObject";

      /**
       * A map of attributes to corrsponding marshallers.
       */
      protected Lookup/*<String, AttributeInfo>*/ m_attributeMap =
         new LinkedHashTab /*<String, AttributeInfo>*/();
     
      /**
       * The ref marshaller for this object instance (need instance so have access to m_sType).
       */
      public final Marshaller REF_MARSHALLER = new MetaclassRefMarshaller();

      /**
       * @param sName The name of the Metaclass to query.
       * @param meta The metaclass this object marshals for
       *             (any key/value pairs not in this class will go under _keys/_values).
       * @param bDynamicMetaclassTypes enable dynamic detection of attributes containing instances
       */
      public MetaclassMarshaller(Metaclass meta, boolean bDynamicMetaclassTypes)
      {
         if (meta == null)
         {
            // done with initialization
            // (this is valid for TransferObject without an existing Metaclass, e.g. jUnit tests)
            return;
         }

         m_sType = meta.getName();

         for (Iterator itr = meta.getAttributeIterator(); itr.hasNext();)
         {
            Attribute attribute = (Attribute)itr.next();

            if (attribute.getVisibility() != Metaclass.PUBLIC)
            {
               continue;
            }

            AttributeInfo attrInfo;

            if (attribute.getType().isPrimitive())
            {
               // can resolve primitive Marshallers immediately since this is not a primitive
               // Marshaller hence no danger of recursion loop
               attrInfo = (Primitive.ANY.equals(attribute.getType()))
                        ? new AttributeInfo(attribute, null, true)
                        : new AttributeInfo(attribute,
                                            getMarshallerByClass(((Primitive)attribute.getType())
                                                                    .getClassObject()),
                                            false);
            }
            else if (attribute.getType() instanceof Metaclass)
            {
               // don't initialize marshaller to avoid recursive creation of self
               attrInfo = new AttributeInfo(attribute, null, bDynamicMetaclassTypes);
            }
            else
            {
               // need value to determine proper marshaller to use (assume it has a static type)
               attrInfo = new AttributeInfo(attribute, null, false);
            }

            m_attributeMap.put(attribute.getName(), attrInfo);
         }
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller#getType()
       */
      public String getType()
      {
         return m_sType;
      }

      /**
       * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller
       *      #marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
       */
      public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
      {
         XMLMarshaller mrsh = (XMLMarshaller)msh;
         TransferObject tobj = (TransferObject)obj;

         // only marshal the class if it's different from the class we are supposed to marshal
         if (m_sType == null || !m_sType.equals(tobj.getClassName()))
         {
            mrsh.marshal(tobj.getClassName(),
                         null,
                         XML.BASE_PREFIX + "class",
                         STRING_MSH,
                         MF_LITERAL);
         }

         mrsh.marshal(tobj.getEventName(),
                      null, XML.BASE_PREFIX + "event", STRING_MSH, MF_LITERAL);
         mrsh.marshal(tobj.getVersion(), null, XML.BASE_PREFIX + "version");

         OID oid = tobj.getOID();

         if (oid == null)
         {
            mrsh.marshal("@" + mrsh.getObjectId(obj),
                         null, XML.BASE_PREFIX + "oid", STRING_MSH, MF_LITERAL);
         }
         else
         {
            mrsh.marshal(oid, null, XML.BASE_PREFIX + "oid", OID_MSH, MF_LITERAL);
         }

         List valueList = null;

         for (PropertyIterator itr = tobj.getIterator(); itr.hasNext();)
         {
            String sName = (String)itr.next();
            AttributeInfo attrInfo = (AttributeInfo)m_attributeMap.get(sName);

            // this is not attribute of this metaclass or value is null hence problem for MS.NET
            if (attrInfo == null || itr.getValue() == null)
            {
               mrsh.marshal(sName, XML.TNS_NS, XML.BASE_PREFIX + "keys", STRING_MSH, MF_NIL);

               if (valueList == null)
               {
                  valueList = new ArrayList();
               }

               valueList.add(itr.getValue());
            }
         }

         if (valueList != null)
         {
            for (int i = 0, n = valueList.size(); i < n; ++i)
            {
               mrsh.marshal(valueList.get(i), XML.TNS_NS, XML.BASE_PREFIX + "values", null, MF_NIL | MF_TYPE);
            }
         }

         for (Lookup.Iterator itr = m_attributeMap.valueIterator(); itr.hasNext();)
         {
            AttributeInfo attrInfo = (AttributeInfo)itr.next();
            Attribute attribute = attrInfo.m_attribute;

            if (attribute.getVisibility() != Metaclass.PUBLIC)
            {
               continue;
            }

            Object value = tobj.findValue(attribute.getName(), Undefined.VALUE);

            if (value == Undefined.VALUE)
            {
               continue;
            }

            Marshaller attrMsh = attrInfo.m_marshaller; // get cached marshaller

            // if this is the first time through marshaller then initialize metaclass marshaller
            if (attrMsh == null)
            {
               if (attribute.getType() instanceof Metaclass)
               {
                  // determine proper marshaller based on attribute to guarantee valid type output
                  attrMsh = mrsh.findMetaclassMarshaller(attribute.getType().getName());
               }
               else if (!attrInfo.m_bDynamicMarshaller && value != null)
               {
                  // might is reachable for non-Metaclass attributes e.g. Throwable
                  attrMsh = mrsh.findMarshaller(value);
               }

               if (attrMsh == null && !attrInfo.m_bDynamicMarshaller && value != null)
               {
                  throw new SOAPMarshallerException("err.rpc.mshType",
                                                    new Object[]{attribute.getType().getName()});
               }

               // cache the marshaller for future reference
               if (attrMsh != null)
               {
                  synchronized(this)
                  {
                     // guarantee all instances will use same marshaller
                     if (attrInfo.m_marshaller == null)
                     {  // store for future lookups
                        attrInfo.m_marshaller = attrMsh;
                     }
                     else
                     {
                        attrMsh = attrInfo.m_marshaller;
                     }
                  }
               }
            }

            // if this is a dynamic marshaller for a metaclass that is not the same as attribute
            if (attrInfo.m_bDynamicMarshaller &&
                attrMsh instanceof MetaclassMarshaller &&
                value instanceof TransferObject &&
                ((TransferObject)value).getClassName() != null && // if unspecified then == default
                !((TransferObject)value).getClassName().equals(attribute.getType().getName()))
            {
               attrMsh = null;
            }

            if (value != null) // do not marshal null values since they will not work with MS.NET
            {
               if (attribute.isCollection())
               {
                  mrsh.marshalInline(((Iterable)value).iterator(), XML.TNS_NS, attrInfo.m_sName, attrMsh, MF_NIL);
               }
               else
               {
                  mrsh.marshal(value, XML.TNS_NS, attrInfo.m_sName, attrMsh, MF_NIL);
               }
            }
         }
      }

      /**
       * Marshaller for ref of this metaclass (non static)
       */
      protected class MetaclassRefMarshaller extends PrimitiveMarshaller
      {
         /**
          * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller#getType()
          */
         public String getType()
         {
            return m_sType;
         }

         /**
          * @see nexj.core.rpc.soap.SOAPMarshaller.PrimitiveMarshaller#getNamespace()
          */
         public String getNamespace()
         {
            return XML.TNS_NS;
         }

         /**
          * @see nexj.core.rpc.soap.SOAPMarshaller.Marshaller#marshalContents(java.lang.Object, nexj.core.rpc.soap.SOAPMarshaller)
          */
         public void marshalContents(Object obj, SOAPMarshaller msh) throws IOException
         {
            XMLMarshaller mrsh = (XMLMarshaller)msh;
            TransferObject tobj = (TransferObject)obj;
            OID oid = tobj.getOID();

            mrsh.marshal(tobj.getClassName(), null, XML.BASE_PREFIX + "class", STRING_MSH, MF_LITERAL);

            mrsh.marshal((oid != null) ? (Object)oid : ("@" + mrsh.getObjectId(obj)), null,
               XML.BASE_PREFIX + "oid", (oid != null) ? OID_MSH : STRING_MSH, MF_LITERAL);
         }
      }

      /**
       * Bean storing attribute information.
       */
      protected static class AttributeInfo
      {
         /**
          * Name of the tag to use.
          */
         public String m_sName;

         /**
          * The attribute in being referenced
          * (final because m_sName value generation depends on it).
          */
         public final Attribute m_attribute;

         /**
          * If the marshaller should be dynamically determined when its type differs from attribute
          */
         public boolean m_bDynamicMarshaller;

         /**
          * The marshaller being used for the attribute.
          */
         public Marshaller m_marshaller;
        
         /**
          * Convinience constructor.
          * @param attribute The attribute being referenced.
          * @param marshaller The Marshaller for the attribute.
          * @param bDynamicMarshaller Determine marshaller dynamically if it doesn't exactly match.
          */
         public AttributeInfo(Attribute attribute, Marshaller marshaller, boolean bDynamicMarshaller)
         {
            m_attribute = attribute;
            m_bDynamicMarshaller = bDynamicMarshaller;
            m_marshaller = marshaller;
            m_sName = XSDGenerator.computeElementName(m_attribute);
         }

         /**
          * @see java.lang.Object#toString()
          */
         public String toString()
         {
            return m_sName + " = (" + m_attribute + ") => (" + m_marshaller + ")";
         }
      }
   }

   /**
    * Interface to distinguish marshallers capable of providing customized element type information.
    */
   protected static interface TypedElementMarshaller extends Marshaller
   {
      /**
       * Return the Namespace URI for the element representing the marshalled object.
       * @param The object to be marshalled that requires a namespace URI.
       * @return The XML namespace URI to use when marshalling this object.
       */
      public String getNamespace(Object obj);

      /**
       * Return the element name for the element representing the marshalled object.
       * @param The object to be marshalled that requires an element name.
       * @return The XML untyped element name to use when marshalling this object.
       */
      public String getElement(Object obj);
   }
}
TOP

Related Classes of nexj.core.rpc.xml.XMLMarshaller$ElementListMarshaller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.