Home | History | Annotate | Download | only in serialization
      1 /*
      2  * Copyright (c) 2003,2004, Stefan Haustein, Oberhausen, Rhld., Germany
      3  *
      4  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
      5  * associated documentation files (the "Software"), to deal in the Software without restriction, including
      6  * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      7  * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
      8  * following conditions:
      9  *
     10  * The above copyright notice and this permission notice shall be included in all copies or substantial
     11  * portions of the Software.
     12  *
     13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
     14  * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
     15  * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     16  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
     17  * USE OR OTHER DEALINGS IN THE SOFTWARE.
     18  */
     19 
     20 package org.ksoap2.serialization;
     21 
     22 import org.ksoap2.SoapEnvelope;
     23 import org.ksoap2.SoapFault;
     24 import org.ksoap2.SoapFault12;
     25 import org.xmlpull.v1.XmlPullParser;
     26 import org.xmlpull.v1.XmlPullParserException;
     27 import org.xmlpull.v1.XmlSerializer;
     28 
     29 import java.io.IOException;
     30 import java.util.Hashtable;
     31 import java.util.Vector;
     32 import java.io.ByteArrayOutputStream;
     33 
     34 import org.kxml2.io.*;
     35 
     36 /**
     37  * @author Stefan Haustein
     38  *
     39  *         This class extends the SoapEnvelope with Soap Serialization functionality.
     40  */
     41 public class SoapSerializationEnvelope extends SoapEnvelope
     42 {
     43     protected static final int QNAME_TYPE = 1;
     44     protected static final int QNAME_NAMESPACE = 0;
     45     protected static final int QNAME_MARSHAL = 3;
     46     private static final String ANY_TYPE_LABEL = "anyType";
     47     private static final String ARRAY_MAPPING_NAME = "Array";
     48     private static final String NULL_LABEL = "null";
     49     private static final String NIL_LABEL = "nil";
     50     private static final String HREF_LABEL = "href";
     51     private static final String ID_LABEL = "id";
     52     private static final String ROOT_LABEL = "root";
     53     private static final String TYPE_LABEL = "type";
     54     private static final String ITEM_LABEL = "item";
     55     private static final String ARRAY_TYPE_LABEL = "arrayType";
     56     static final Marshal DEFAULT_MARSHAL = new DM();
     57     public Hashtable properties = new Hashtable();
     58 
     59     Hashtable idMap = new Hashtable();
     60     Vector multiRef; // = new Vector();
     61 
     62     /**
     63      * Set this variable to true if you don't want that type definitions for complex types/objects
     64      * are automatically generated (with type "anyType") in the XML-Request, if you don't call the
     65      * Method addMapping. This is needed by some Servers which have problems with these type-definitions.
     66      */
     67     public boolean implicitTypes;
     68 
     69     /**
     70      * Set this variable to true for compatibility with what seems to be the default encoding for
     71      * .Net-Services. This feature is an extremely ugly hack. A much better option is to change the
     72      * configuration of the .Net-Server to standard Soap Serialization!
     73      */
     74 
     75     public boolean dotNet;
     76 
     77     /**
     78      * Set this variable to true if you prefer to silently skip unknown properties.
     79      * {@link RuntimeException} will be thrown otherwise.
     80      */
     81     public boolean avoidExceptionForUnknownProperty;
     82 
     83     /**
     84      * Map from XML qualified names to Java classes
     85      */
     86 
     87     protected Hashtable qNameToClass = new Hashtable();
     88 
     89     /**
     90      * Map from Java class names to XML name and namespace pairs
     91      */
     92 
     93     protected Hashtable classToQName = new Hashtable();
     94 
     95     /**
     96      * Set to true to add and ID and ROOT label to the envelope. Change to false for compatibility with WSDL.
     97      */
     98     protected boolean addAdornments = true;
     99 
    100     public SoapSerializationEnvelope(int version)
    101     {
    102         super(version);
    103         addMapping(enc, ARRAY_MAPPING_NAME, PropertyInfo.VECTOR_CLASS);
    104         DEFAULT_MARSHAL.register(this);
    105     }
    106 
    107     /**
    108      * @return the addAdornments
    109      */
    110     public boolean isAddAdornments()
    111     {
    112         return addAdornments;
    113     }
    114 
    115     /**
    116      * @param addAdornments
    117      *            the addAdornments to set
    118      */
    119     public void setAddAdornments(boolean addAdornments)
    120     {
    121         this.addAdornments = addAdornments;
    122     }
    123 
    124     /**
    125      * Set the bodyOut to be empty so that no un-needed xml is create. The null value for bodyOut will
    126      * cause #writeBody to skip writing anything redundant.
    127      * @param emptyBody
    128      * @see "http://code.google.com/p/ksoap2-android/issues/detail?id=77"
    129      */
    130     public void setBodyOutEmpty(boolean emptyBody) {
    131         if (emptyBody) {
    132             bodyOut = null;
    133         }
    134     }
    135 
    136     public void parseBody(XmlPullParser parser) throws IOException, XmlPullParserException
    137     {
    138         bodyIn = null;
    139         parser.nextTag();
    140         if (parser.getEventType() == XmlPullParser.START_TAG && parser.getNamespace().equals(env)
    141                 && parser.getName().equals("Fault")) {
    142             SoapFault fault;
    143             if (this.version < SoapEnvelope.VER12) {
    144                 fault = new SoapFault(this.version);
    145             } else {
    146                 fault = new SoapFault12(this.version);
    147             }
    148             fault.parse(parser);
    149             bodyIn = fault;
    150         } else {
    151             while (parser.getEventType() == XmlPullParser.START_TAG) {
    152                 String rootAttr = parser.getAttributeValue(enc, ROOT_LABEL);
    153 
    154                 Object o = read(parser, null, -1, parser.getNamespace(), parser.getName(),
    155                         PropertyInfo.OBJECT_TYPE);
    156                 if ("1".equals(rootAttr) || bodyIn == null) {
    157                     bodyIn = o;
    158                 }
    159                 parser.nextTag();
    160             }
    161         }
    162     }
    163 
    164     /** Read a SoapObject. This extracts any attributes and then reads the object as a KvmSerializable. */
    165     protected void readSerializable(XmlPullParser parser, SoapObject obj) throws IOException,
    166             XmlPullParserException
    167     {
    168         for (int counter = 0; counter < parser.getAttributeCount(); counter++) {
    169             String attributeName = parser.getAttributeName(counter);
    170             String value = parser.getAttributeValue(counter);
    171             ((SoapObject) obj).addAttribute(attributeName, value);
    172         }
    173         readSerializable(parser, (KvmSerializable) obj);
    174     }
    175 
    176     /** Read a KvmSerializable. */
    177     protected void readSerializable(XmlPullParser parser, KvmSerializable obj) throws IOException,
    178             XmlPullParserException
    179     {
    180         while (parser.nextTag() != XmlPullParser.END_TAG) {
    181             String name = parser.getName();
    182             if (!implicitTypes || !(obj instanceof SoapObject)) {
    183                 PropertyInfo info = new PropertyInfo();
    184                 int propertyCount = obj.getPropertyCount();
    185                 boolean propertyFound = false;
    186 
    187                 for (int i = 0; i < propertyCount && !propertyFound; i++) {
    188                     info.clear();
    189                     obj.getPropertyInfo(i, properties, info);
    190 
    191                     if ((name.equals(info.name) && info.namespace == null)
    192                             ||
    193                             (name.equals(info.name) && parser.getNamespace().equals(info.namespace))) {
    194                         propertyFound = true;
    195                         obj.setProperty(i, read(parser, obj, i, null, null, info));
    196                     }
    197                 }
    198 
    199                 if (!propertyFound) {
    200                     if (avoidExceptionForUnknownProperty) {
    201                         // Dummy loop to read until corresponding END tag
    202                         while (parser.next() != XmlPullParser.END_TAG
    203                                 || !name.equals(parser.getName())) {
    204                         }
    205                         ;
    206                     } else {
    207                         throw new RuntimeException("Unknown Property: " + name);
    208                     }
    209                 }
    210             } else {
    211                 // I can only make this work for SoapObjects - hence the check above
    212                 // I don't understand namespaces well enough to know whether it is correct in the next line...
    213                 ((SoapObject) obj).addProperty(parser.getName(),
    214                         read(parser, obj, obj.getPropertyCount(),
    215                                 ((SoapObject) obj).getNamespace(), name, PropertyInfo.OBJECT_TYPE));
    216             }
    217         }
    218         parser.require(XmlPullParser.END_TAG, null, null);
    219     }
    220 
    221     /**
    222      * If the type of the object cannot be determined, and thus no Marshal class can handle the object, this
    223      * method is called. It will build either a SoapPrimitive or a SoapObject
    224      *
    225      * @param parser
    226      * @param typeNamespace
    227      * @param typeName
    228      * @return unknownObject wrapped as a SoapPrimitive or SoapObject
    229      * @throws IOException
    230      * @throws XmlPullParserException
    231      */
    232 
    233     protected Object readUnknown(XmlPullParser parser, String typeNamespace, String typeName)
    234             throws IOException, XmlPullParserException {
    235         String name = parser.getName();
    236         String namespace = parser.getNamespace();
    237 
    238         // cache the attribute info list from the current element before we move on
    239         Vector attributeInfoVector = new Vector();
    240         for (int attributeCount = 0; attributeCount < parser.getAttributeCount(); attributeCount++) {
    241             AttributeInfo attributeInfo = new AttributeInfo();
    242             attributeInfo.setName(parser.getAttributeName(attributeCount));
    243             attributeInfo.setValue(parser.getAttributeValue(attributeCount));
    244             attributeInfo.setNamespace(parser.getAttributeNamespace(attributeCount));
    245             attributeInfo.setType(parser.getAttributeType(attributeCount));
    246             attributeInfoVector.addElement(attributeInfo);
    247         }
    248 
    249         parser.next(); // move to text, inner start tag or end tag
    250         Object result = null;
    251         String text = null;
    252         if (parser.getEventType() == XmlPullParser.TEXT) {
    253             text = parser.getText();
    254             SoapPrimitive sp = new SoapPrimitive(typeNamespace, typeName, text);
    255             result = sp;
    256             // apply all the cached attribute info list before we add the property and descend further for parsing
    257             for (int i = 0; i < attributeInfoVector.size(); i++) {
    258                 sp.addAttribute((AttributeInfo) attributeInfoVector.elementAt(i));
    259             }
    260             parser.next();
    261         } else if (parser.getEventType() == XmlPullParser.END_TAG) {
    262             SoapObject so = new SoapObject(typeNamespace, typeName);
    263             // apply all the cached attribute info list before we add the property and descend further for parsing
    264             for (int i = 0; i < attributeInfoVector.size(); i++) {
    265                 so.addAttribute((AttributeInfo) attributeInfoVector.elementAt(i));
    266             }
    267             result = so;
    268         }
    269 
    270         if (parser.getEventType() == XmlPullParser.START_TAG) {
    271             if (text != null && text.trim().length() != 0) {
    272                 throw new RuntimeException("Malformed input: Mixed content");
    273             }
    274             SoapObject so = new SoapObject(typeNamespace, typeName);
    275             // apply all the cached attribute info list before we add the property and descend further for parsing
    276             for (int i = 0; i < attributeInfoVector.size(); i++) {
    277                 so.addAttribute((AttributeInfo) attributeInfoVector.elementAt(i));
    278             }
    279 
    280             while (parser.getEventType() != XmlPullParser.END_TAG) {
    281                 so.addProperty(parser.getName(),
    282                         read(parser, so, so.getPropertyCount(), null, null,
    283                                 PropertyInfo.OBJECT_TYPE));
    284                 parser.nextTag();
    285             }
    286             result = so;
    287         }
    288         parser.require(XmlPullParser.END_TAG, namespace, name);
    289         return result;
    290     }
    291 
    292     private int getIndex(String value, int start, int dflt) {
    293         if (value == null) {
    294             return dflt;
    295         }
    296         return value.length() - start < 3 ? dflt : Integer.parseInt(value.substring(start + 1,
    297                 value.length() - 1));
    298     }
    299 
    300     protected void readVector(XmlPullParser parser, Vector v, PropertyInfo elementType)
    301             throws IOException,
    302             XmlPullParserException {
    303         String namespace = null;
    304         String name = null;
    305         int size = v.size();
    306         boolean dynamic = true;
    307         String type = parser.getAttributeValue(enc, ARRAY_TYPE_LABEL);
    308         if (type != null) {
    309             int cut0 = type.indexOf(':');
    310             int cut1 = type.indexOf("[", cut0);
    311             name = type.substring(cut0 + 1, cut1);
    312             String prefix = cut0 == -1 ? "" : type.substring(0, cut0);
    313             namespace = parser.getNamespace(prefix);
    314             size = getIndex(type, cut1, -1);
    315             if (size != -1) {
    316                 v.setSize(size);
    317                 dynamic = false;
    318             }
    319         }
    320         if (elementType == null) {
    321             elementType = PropertyInfo.OBJECT_TYPE;
    322         }
    323         parser.nextTag();
    324         int position = getIndex(parser.getAttributeValue(enc, "offset"), 0, 0);
    325         while (parser.getEventType() != XmlPullParser.END_TAG) {
    326             // handle position
    327             position = getIndex(parser.getAttributeValue(enc, "position"), 0, position);
    328             if (dynamic && position >= size) {
    329                 size = position + 1;
    330                 v.setSize(size);
    331             }
    332             // implicit handling of position exceeding specified size
    333             v.setElementAt(read(parser, v, position, namespace, name, elementType), position);
    334             position++;
    335             parser.nextTag();
    336         }
    337         parser.require(XmlPullParser.END_TAG, null, null);
    338     }
    339 
    340     /**
    341      * Builds an object from the XML stream. This method is public for usage in conjuction with Marshal
    342      * subclasses. Precondition: On the start tag of the object or property, so href can be read.
    343      */
    344 
    345     public Object read(XmlPullParser parser, Object owner, int index, String namespace,
    346             String name,
    347             PropertyInfo expected) throws IOException, XmlPullParserException {
    348         String elementName = parser.getName();
    349         String href = parser.getAttributeValue(null, HREF_LABEL);
    350         Object obj;
    351         if (href != null) {
    352             if (owner == null) {
    353                 throw new RuntimeException("href at root level?!?");
    354             }
    355             href = href.substring(1);
    356             obj = idMap.get(href);
    357             if (obj == null || obj instanceof FwdRef) {
    358                 FwdRef f = new FwdRef();
    359                 f.next = (FwdRef) obj;
    360                 f.obj = owner;
    361                 f.index = index;
    362                 idMap.put(href, f);
    363                 obj = null;
    364             }
    365             parser.nextTag(); // start tag
    366             parser.require(XmlPullParser.END_TAG, null, elementName);
    367         } else {
    368             String nullAttr = parser.getAttributeValue(xsi, NIL_LABEL);
    369             String id = parser.getAttributeValue(null, ID_LABEL);
    370             if (nullAttr == null) {
    371                 nullAttr = parser.getAttributeValue(xsi, NULL_LABEL);
    372             }
    373             if (nullAttr != null && SoapEnvelope.stringToBoolean(nullAttr)) {
    374                 obj = null;
    375                 parser.nextTag();
    376                 parser.require(XmlPullParser.END_TAG, null, elementName);
    377             } else {
    378                 String type = parser.getAttributeValue(xsi, TYPE_LABEL);
    379                 if (type != null) {
    380                     int cut = type.indexOf(':');
    381                     name = type.substring(cut + 1);
    382                     String prefix = cut == -1 ? "" : type.substring(0, cut);
    383                     namespace = parser.getNamespace(prefix);
    384                 } else if (name == null && namespace == null) {
    385                     if (parser.getAttributeValue(enc, ARRAY_TYPE_LABEL) != null) {
    386                         namespace = enc;
    387                         name = ARRAY_MAPPING_NAME;
    388                     } else {
    389                         Object[] names = getInfo(expected.type, null);
    390                         namespace = (String) names[0];
    391                         name = (String) names[1];
    392                     }
    393                 }
    394                 // be sure to set this flag if we don't know the types.
    395                 if (type == null) {
    396                     implicitTypes = true;
    397                 }
    398                 obj = readInstance(parser, namespace, name, expected);
    399                 if (obj == null) {
    400                     obj = readUnknown(parser, namespace, name);
    401                 }
    402             }
    403             // finally, care about the id....
    404             if (id != null) {
    405                 Object hlp = idMap.get(id);
    406                 if (hlp instanceof FwdRef) {
    407                     FwdRef f = (FwdRef) hlp;
    408                     do {
    409                         if (f.obj instanceof KvmSerializable) {
    410                             ((KvmSerializable) f.obj).setProperty(f.index, obj);
    411                         } else {
    412                             ((Vector) f.obj).setElementAt(obj, f.index);
    413                         }
    414                         f = f.next;
    415                     } while (f != null);
    416                 } else if (hlp != null) {
    417                     throw new RuntimeException("double ID");
    418                 }
    419                 idMap.put(id, obj);
    420             }
    421         }
    422 
    423         parser.require(XmlPullParser.END_TAG, null, elementName);
    424         return obj;
    425     }
    426 
    427     /**
    428      * Returns a new object read from the given parser. If no mapping is found, null is returned. This method
    429      * is used by the SoapParser in order to convert the XML code to Java objects.
    430      */
    431     public Object readInstance(XmlPullParser parser, String namespace, String name,
    432             PropertyInfo expected)
    433             throws IOException, XmlPullParserException {
    434         Object obj = qNameToClass.get(new SoapPrimitive(namespace, name, null));
    435         if (obj == null) {
    436             return null;
    437         }
    438         if (obj instanceof Marshal) {
    439             return ((Marshal) obj).readInstance(parser, namespace, name, expected);
    440         } else if (obj instanceof SoapObject) {
    441             obj = ((SoapObject) obj).newInstance();
    442         } else if (obj == SoapObject.class) {
    443             obj = new SoapObject(namespace, name);
    444         } else {
    445             try {
    446                 obj = ((Class) obj).newInstance();
    447             } catch (Exception e) {
    448                 throw new RuntimeException(e.toString());
    449             }
    450         }
    451         // ok, obj is now the instance, fill it....
    452         if (obj instanceof SoapObject) {
    453             readSerializable(parser, (SoapObject) obj);
    454         } else if (obj instanceof KvmSerializable) {
    455             readSerializable(parser, (KvmSerializable) obj);
    456         } else if (obj instanceof Vector) {
    457             readVector(parser, (Vector) obj, expected.elementType);
    458         } else {
    459             throw new RuntimeException("no deserializer for " + obj.getClass());
    460         }
    461         return obj;
    462     }
    463 
    464     /**
    465      * Returns a string array containing the namespace, name, id and Marshal object for the given java object.
    466      * This method is used by the SoapWriter in order to map Java objects to the corresponding SOAP section
    467      * five XML code.
    468      */
    469     public Object[] getInfo(Object type, Object instance) {
    470         if (type == null) {
    471             if (instance instanceof SoapObject || instance instanceof SoapPrimitive) {
    472                 type = instance;
    473             } else {
    474                 type = instance.getClass();
    475             }
    476         }
    477         if (type instanceof SoapObject) {
    478             SoapObject so = (SoapObject) type;
    479             return new Object[] {
    480                     so.getNamespace(), so.getName(), null, null
    481             };
    482         }
    483         if (type instanceof SoapPrimitive) {
    484             SoapPrimitive sp = (SoapPrimitive) type;
    485             return new Object[] {
    486                     sp.getNamespace(), sp.getName(), null, DEFAULT_MARSHAL
    487             };
    488         }
    489         if ((type instanceof Class) && type != PropertyInfo.OBJECT_CLASS) {
    490             Object[] tmp = (Object[]) classToQName.get(((Class) type).getName());
    491             if (tmp != null) {
    492                 return tmp;
    493             }
    494         }
    495         return new Object[] {
    496                 xsd, ANY_TYPE_LABEL, null, null
    497         };
    498     }
    499 
    500     /**
    501      * Defines a direct mapping from a namespace and name to a java class (and vice versa), using the given
    502      * marshal mechanism
    503      */
    504     public void addMapping(String namespace, String name, Class clazz, Marshal marshal) {
    505         qNameToClass
    506                 .put(new SoapPrimitive(namespace, name, null), marshal == null ? (Object) clazz
    507                         : marshal);
    508         classToQName.put(clazz.getName(), new Object[] {
    509                 namespace, name, null, marshal
    510         });
    511     }
    512 
    513     /**
    514      * Defines a direct mapping from a namespace and name to a java class (and vice versa)
    515      */
    516     public void addMapping(String namespace, String name, Class clazz) {
    517         addMapping(namespace, name, clazz, null);
    518     }
    519 
    520     /**
    521      * Adds a SoapObject to the class map. During parsing, objects of the given type (namespace/name) will be
    522      * mapped to corresponding copies of the given SoapObject, maintaining the structure of the template.
    523      */
    524     public void addTemplate(SoapObject so) {
    525         qNameToClass.put(new SoapPrimitive(so.namespace, so.name, null), so);
    526     }
    527 
    528     /**
    529      * Response from the soap call. Pulls the object from the wrapper object and returns it.
    530      *
    531      * @since 2.0.3
    532      * @return response from the soap call.
    533      * @throws SoapFault
    534      */
    535     public Object getResponse() throws SoapFault {
    536         if (bodyIn instanceof SoapFault) {
    537             throw (SoapFault) bodyIn;
    538         }
    539         KvmSerializable ks = (KvmSerializable) bodyIn;
    540 
    541         if (ks.getPropertyCount() == 0) {
    542             return null;
    543         } else if (ks.getPropertyCount() == 1) {
    544             return ks.getProperty(0);
    545         } else {
    546             Vector ret = new Vector();
    547             for (int i = 0; i < ks.getPropertyCount(); i++) {
    548                 ret.add(ks.getProperty(i));
    549             }
    550             return ret;
    551         }
    552     }
    553 
    554     /**
    555      * Serializes the request object to the given XmlSerliazer object
    556      *
    557      * @param writer
    558      *            XmlSerializer object to write the body into.
    559      */
    560     public void writeBody(XmlSerializer writer) throws IOException {
    561         // allow an empty body without any tags in it
    562         // see http://code.google.com/p/ksoap2-android/issues/detail?id=77
    563         if (bodyOut != null) {
    564             multiRef = new Vector();
    565             multiRef.addElement(bodyOut);
    566             Object[] qName = getInfo(null, bodyOut);
    567             writer.startTag((dotNet) ? "" : (String) qName[QNAME_NAMESPACE],
    568                     (String) qName[QNAME_TYPE]); //<spp:sppPostDevData
    569             if (dotNet) {
    570                 writer.attribute(null, "xmlns", (String) qName[QNAME_NAMESPACE]);
    571             }
    572             if (addAdornments) {
    573                 writer.attribute(null, ID_LABEL, qName[2] == null ? ("o" + 0) : (String) qName[2]);
    574                 writer.attribute(enc, ROOT_LABEL, "1");
    575             }
    576             writeElement(writer, bodyOut, null, qName[QNAME_MARSHAL]); //....
    577             writer.endTag((dotNet) ? "" : (String) qName[QNAME_NAMESPACE],
    578                     (String) qName[QNAME_TYPE]);//</spp:sppPostDevData>
    579         }
    580     }
    581 
    582     /**
    583      * Writes the body of an SoapObject. This method write the attributes and then calls
    584      * "writeObjectBody (writer, (KvmSerializable)obj);"
    585      */
    586     public void writeObjectBody(XmlSerializer writer, SoapObject obj) throws IOException {
    587         SoapObject soapObject = (SoapObject) obj;
    588         int cnt = soapObject.getAttributeCount();
    589         for (int counter = 0; counter < cnt; counter++) {
    590             AttributeInfo attributeInfo = new AttributeInfo();
    591             soapObject.getAttributeInfo(counter, attributeInfo);
    592             writer.attribute(attributeInfo.getNamespace(), attributeInfo.getName(), attributeInfo
    593                     .getValue()
    594                     .toString());
    595         }
    596         writeObjectBody(writer, (KvmSerializable) obj);
    597     }
    598 
    599     /**
    600      * Writes the body of an KvmSerializable object. This method is public for access from Marshal subclasses.
    601      */
    602     public void writeObjectBody(XmlSerializer writer, KvmSerializable obj) throws IOException {
    603         int cnt = obj.getPropertyCount();
    604         PropertyInfo propertyInfo = new PropertyInfo();
    605         String namespace;
    606         String name;
    607         String type;
    608         for (int i = 0; i < cnt; i++) {
    609             // get the property
    610             Object prop = obj.getProperty(i);
    611             // and importantly also get the property info which holds the name potentially!
    612             obj.getPropertyInfo(i, properties, propertyInfo);
    613 
    614             if (!(prop instanceof SoapObject)) {
    615                 // prop is a PropertyInfo
    616                 if ((propertyInfo.flags & PropertyInfo.TRANSIENT) == 0) {
    617                     writer.startTag(propertyInfo.namespace, propertyInfo.name);
    618                     writeProperty(writer, obj.getProperty(i), propertyInfo);
    619                     writer.endTag(propertyInfo.namespace, propertyInfo.name);
    620                 }
    621             } else {
    622                 // prop is a SoapObject
    623                 SoapObject nestedSoap = (SoapObject) prop;
    624                 // lets get the info from the soap object itself
    625                 Object[] qName = getInfo(null, nestedSoap);
    626                 namespace = (String) qName[QNAME_NAMESPACE];
    627                 type = (String) qName[QNAME_TYPE];
    628 
    629                 // prefer the name from the property info
    630                 if (propertyInfo.name != null && propertyInfo.name.length() > 0) {
    631                     name = propertyInfo.name;
    632                 } else {
    633                     name = (String) qName[QNAME_TYPE];
    634                 }
    635 
    636                 // treat MO data as CDATA
    637                 if (name.equals("DevInfo") || name.equals("DevDetail")
    638                         || name.equals("PerProviderSubscription") || // format v4
    639                         name.equals("MgmtTree") // format v6
    640                 ) {
    641                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
    642                     XmlSerializer xw = new KXmlSerializer();
    643                     xw.setOutput(bos, "UTF-8");
    644                     xw.startTag((dotNet) ? "" : namespace, name);
    645                     if (!implicitTypes) {
    646                         String prefix = writer.getPrefix(namespace, true);
    647                         writer.attribute(xsi, TYPE_LABEL, prefix + ":" + type);
    648                     }
    649                     writeObjectBody(xw, nestedSoap);
    650                     xw.endTag((dotNet) ? "" : namespace, name);
    651                     xw.flush();
    652                     //bos.write('\r');
    653                     //bos.write('\n');
    654                     bos.flush();
    655                     writer.cdsect(bos.toString());
    656                 }
    657                 else
    658                 {
    659                     writer.startTag((dotNet) ? "" : namespace, name);
    660                     if (!implicitTypes) {
    661                         String prefix = writer.getPrefix(namespace, true);
    662                         writer.attribute(xsi, TYPE_LABEL, prefix + ":" + type);
    663                     }
    664                     writeObjectBody(writer, nestedSoap);
    665                     writer.endTag((dotNet) ? "" : namespace, name);
    666                 }
    667             }
    668         }
    669     }
    670 
    671     protected void writeProperty(XmlSerializer writer, Object obj, PropertyInfo type)
    672             throws IOException {
    673         if (obj == null) {
    674             ///M: Modify for HS20
    675             //writer.attribute(xsi, version >= VER12 ? NIL_LABEL : NULL_LABEL, "true");
    676             return;
    677         }
    678         Object[] qName = getInfo(null, obj);
    679         if (type.multiRef || qName[2] != null) {
    680             int i = multiRef.indexOf(obj);
    681             if (i == -1) {
    682                 i = multiRef.size();
    683                 multiRef.addElement(obj);
    684             }
    685             writer.attribute(null, HREF_LABEL, qName[2] == null ? ("#o" + i) : "#" + qName[2]);
    686         } else {
    687             if (!implicitTypes || obj.getClass() != type.type) {
    688                 String prefix = writer.getPrefix((String) qName[QNAME_NAMESPACE], true);
    689                 writer.attribute(xsi, TYPE_LABEL, prefix + ":" + qName[QNAME_TYPE]);
    690             }
    691             writeElement(writer, obj, type, qName[QNAME_MARSHAL]);
    692         }
    693     }
    694 
    695     private void writeElement(XmlSerializer writer, Object element, PropertyInfo type,
    696             Object marshal)
    697             throws IOException {
    698         if (marshal != null) {
    699             ((Marshal) marshal).writeInstance(writer, element);
    700         } else if (element instanceof SoapObject) {
    701             writeObjectBody(writer, (SoapObject) element);
    702         } else if (element instanceof KvmSerializable) {
    703             writeObjectBody(writer, (KvmSerializable) element);
    704         } else if (element instanceof Vector) {
    705             writeVectorBody(writer, (Vector) element, type.elementType);
    706         } else {
    707             throw new RuntimeException("Cannot serialize: " + element);
    708         }
    709     }
    710 
    711     protected void writeVectorBody(XmlSerializer writer, Vector vector, PropertyInfo elementType)
    712             throws IOException {
    713         String itemsTagName = ITEM_LABEL;
    714         String itemsNamespace = null;
    715 
    716         if (elementType == null) {
    717             elementType = PropertyInfo.OBJECT_TYPE;
    718         } else if (elementType instanceof PropertyInfo) {
    719             if (elementType.name != null) {
    720                 itemsTagName = elementType.name;
    721                 itemsNamespace = elementType.namespace;
    722             }
    723         }
    724 
    725         int cnt = vector.size();
    726         Object[] arrType = getInfo(elementType.type, null);
    727 
    728         // This removes the arrayType attribute from the xml for arrays(required for most .Net services to work)
    729         if (!implicitTypes) {
    730             writer.attribute(enc, ARRAY_TYPE_LABEL, writer.getPrefix((String) arrType[0], false)
    731                     + ":"
    732                     + arrType[1] + "[" + cnt + "]");
    733         }
    734 
    735         boolean skipped = false;
    736         for (int i = 0; i < cnt; i++) {
    737             if (vector.elementAt(i) == null) {
    738                 skipped = true;
    739             } else {
    740                 writer.startTag(itemsNamespace, itemsTagName);
    741                 if (skipped) {
    742                     writer.attribute(enc, "position", "[" + i + "]");
    743                     skipped = false;
    744                 }
    745                 writeProperty(writer, vector.elementAt(i), elementType);
    746                 writer.endTag(itemsNamespace, itemsTagName);
    747             }
    748         }
    749     }
    750 }
    751