• 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,
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 
SoapSerializationEnvelope(int version)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      */
isAddAdornments()118     public boolean isAddAdornments() {
119         return addAdornments;
120     }
121 
122     /**
123      * @param addAdornments the addAdornments to set
124      */
setAddAdornments(boolean addAdornments)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      */
setBodyOutEmpty(boolean emptyBody)136     public void setBodyOutEmpty(boolean emptyBody) {
137         if (emptyBody) {
138             bodyOut = null;
139         }
140     }
141 
parseBody(XmlPullParser parser)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      */
readSerializable(XmlPullParser parser, SoapObject obj)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      */
readSerializable(XmlPullParser parser, KvmSerializable obj)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 
readUnknown(XmlPullParser parser, String typeNamespace, String typeName)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 
getIndex(String value, int start, int dflt)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 
readVector(XmlPullParser parser, Vector v, PropertyInfo elementType)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      */
getIdFromHref(String hrefValue)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 
read(XmlPullParser parser, Object owner, int index, String namespace, String name, PropertyInfo expected)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 
resolveReference(String id, Object obj)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      */
readInstance(XmlPullParser parser, String namespace, String name, PropertyInfo expected)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      */
getInfo(Object type, Object instance)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      */
addMapping(String namespace, String name, Class clazz, Marshal marshal)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      */
addMapping(String namespace, String name, Class clazz)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      */
addTemplate(SoapObject so)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      */
getResponse()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      */
writeBody(XmlSerializer writer)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 
writeAttributes(XmlSerializer writer, HasAttributes obj)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 
writeArrayListBodyWithAttributes(XmlSerializer writer, KvmSerializable obj)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 
writeObjectBodyWithAttributes(XmlSerializer writer, KvmSerializable obj)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      */
writeObjectBody(XmlSerializer writer, KvmSerializable obj)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 
writeInnerText(XmlSerializer writer, KvmSerializable obj)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 
writeProperty(XmlSerializer writer, Object obj, PropertyInfo type)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 
writeElement(XmlSerializer writer, Object element, PropertyInfo type, Object marshal)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 
writeArrayListBody(XmlSerializer writer, ArrayList list)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 
writeVectorBody(XmlSerializer writer, Vector vector, PropertyInfo elementType)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