• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SoapSerializationEnvelope(int version)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      */
isAddAdornments()110     public boolean isAddAdornments()
111     {
112         return addAdornments;
113     }
114 
115     /**
116      * @param addAdornments
117      *            the addAdornments to set
118      */
setAddAdornments(boolean addAdornments)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      */
setBodyOutEmpty(boolean emptyBody)130     public void setBodyOutEmpty(boolean emptyBody) {
131         if (emptyBody) {
132             bodyOut = null;
133         }
134     }
135 
parseBody(XmlPullParser parser)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. */
readSerializable(XmlPullParser parser, SoapObject obj)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. */
readSerializable(XmlPullParser parser, KvmSerializable obj)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 
readUnknown(XmlPullParser parser, String typeNamespace, String typeName)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 
getIndex(String value, int start, int dflt)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 
readVector(XmlPullParser parser, Vector v, PropertyInfo elementType)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 
read(XmlPullParser parser, Object owner, int index, String namespace, String name, PropertyInfo expected)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      */
readInstance(XmlPullParser parser, String namespace, String name, PropertyInfo expected)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      */
getInfo(Object type, Object instance)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      */
addMapping(String namespace, String name, Class clazz, Marshal marshal)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      */
addMapping(String namespace, String name, Class clazz)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      */
addTemplate(SoapObject so)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      */
getResponse()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      */
writeBody(XmlSerializer writer)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      */
writeObjectBody(XmlSerializer writer, SoapObject obj)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      */
writeObjectBody(XmlSerializer writer, KvmSerializable obj)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 
writeProperty(XmlSerializer writer, Object obj, PropertyInfo type)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 
writeElement(XmlSerializer writer, Object element, PropertyInfo type, Object marshal)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 
writeVectorBody(XmlSerializer writer, Vector vector, PropertyInfo elementType)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