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