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