001 /* 002 * Copyright 2001-2002,2004 The Apache Software Foundation. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.apache.axis.encoding.ser; 018 019 import java.io.CharArrayWriter; 020 import java.io.Serializable; 021 import java.lang.reflect.Constructor; 022 import java.util.Map; 023 024 import javax.xml.namespace.QName; 025 026 import org.apache.axis.Constants; 027 import org.apache.axis.components.logger.LogFactory; 028 import org.apache.axis.description.ElementDesc; 029 import org.apache.axis.description.FieldDesc; 030 import org.apache.axis.description.TypeDesc; 031 import org.apache.axis.encoding.ConstructorTarget; 032 import org.apache.axis.encoding.DeserializationContext; 033 import org.apache.axis.encoding.Deserializer; 034 import org.apache.axis.encoding.DeserializerImpl; 035 import org.apache.axis.encoding.Target; 036 import org.apache.axis.encoding.TypeMapping; 037 import org.apache.axis.message.MessageElement; 038 import org.apache.axis.message.SOAPHandler; 039 import org.apache.axis.soap.SOAPConstants; 040 import org.apache.axis.utils.BeanPropertyDescriptor; 041 import org.apache.axis.utils.Messages; 042 import org.apache.commons.logging.Log; 043 import org.xml.sax.Attributes; 044 import org.xml.sax.SAXException; 045 046 /** 047 * General purpose deserializer for an arbitrary java bean. 048 * 049 * @author Sam Ruby <rubys@us.ibm.com> 050 * @author Rich Scheuerle <scheu@us.ibm.com> 051 * @author Tom Jordahl <tomj@macromedia.com> 052 */ 053 public class BeanDeserializer extends DeserializerImpl implements Serializable 054 { 055 protected static Log log = 056 LogFactory.getLog(BeanDeserializer.class.getName()); 057 058 private final CharArrayWriter val = new CharArrayWriter(); 059 060 QName xmlType; 061 Class javaType; 062 protected Map propertyMap = null; 063 protected QName prevQName; 064 065 /** 066 * Constructor if no default constructor 067 */ 068 protected Constructor constructorToUse = null; 069 070 /** 071 * Constructor Target object to use (if constructorToUse != null) 072 */ 073 protected Target constructorTarget = null; 074 075 /** Type metadata about this class for XML deserialization */ 076 protected TypeDesc typeDesc = null; 077 078 // This counter is updated to deal with deserialize collection properties 079 protected int collectionIndex = -1; 080 081 protected SimpleDeserializer cacheStringDSer = null; 082 protected QName cacheXMLType = null; 083 084 // Construct BeanSerializer for the indicated class/qname 085 public BeanDeserializer(Class javaType, QName xmlType) { 086 this(javaType, xmlType, TypeDesc.getTypeDescForClass(javaType)); 087 } 088 089 // Construct BeanDeserializer for the indicated class/qname and meta Data 090 public BeanDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc ) { 091 this(javaType, xmlType, typeDesc, 092 BeanDeserializerFactory.getProperties(javaType, typeDesc)); 093 } 094 095 // Construct BeanDeserializer for the indicated class/qname and meta Data 096 public BeanDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc, 097 Map propertyMap ) { 098 this.xmlType = xmlType; 099 this.javaType = javaType; 100 this.typeDesc = typeDesc; 101 this.propertyMap = propertyMap; 102 103 // create a value 104 try { 105 value=javaType.newInstance(); 106 } catch (Exception e) { 107 // Don't process the exception at this point. 108 // This is defered until the call to startElement 109 // which will throw the exception. 110 } 111 } 112 113 /** 114 * startElement 115 * 116 * The ONLY reason that this method is overridden is so that 117 * the object value can be set or a reasonable exception is thrown 118 * indicating that the object cannot be created. This is done 119 * at this point so that it occurs BEFORE href/id processing. 120 * @param namespace is the namespace of the element 121 * @param localName is the name of the element 122 * @param prefix is the prefix of the element 123 * @param attributes are the attributes on the element...used to get the 124 * type 125 * @param context is the DeserializationContext 126 */ 127 public void startElement(String namespace, String localName, 128 String prefix, Attributes attributes, 129 DeserializationContext context) 130 throws SAXException 131 { 132 // Create the bean object if it was not already 133 // created in the constructor. 134 if (value == null) { 135 try { 136 value=javaType.newInstance(); 137 } catch (Exception e) { 138 // Use first found constructor. 139 // Note : the right way is to use XML mapping information 140 // for example JSR 109's constructor-parameter-order 141 Constructor[] constructors = javaType.getConstructors(); 142 if (constructors.length > 0) { 143 constructorToUse = constructors[0]; 144 } 145 146 // Failed to create an object if no constructor 147 if (constructorToUse == null) { 148 throw new SAXException(Messages.getMessage("cantCreateBean00", 149 javaType.getName(), 150 e.toString())); 151 } 152 } 153 } 154 // Invoke super.startElement to do the href/id processing. 155 super.startElement(namespace, localName, 156 prefix, attributes, context); 157 } 158 159 /** 160 * Deserializer interface called on each child element encountered in 161 * the XML stream. 162 * @param namespace is the namespace of the child element 163 * @param localName is the local name of the child element 164 * @param prefix is the prefix used on the name of the child element 165 * @param attributes are the attributes of the child element 166 * @param context is the deserialization context. 167 * @return is a Deserializer to use to deserialize a child (must be 168 * a derived class of SOAPHandler) or null if no deserialization should 169 * be performed. 170 */ 171 public SOAPHandler onStartChild(String namespace, 172 String localName, 173 String prefix, 174 Attributes attributes, 175 DeserializationContext context) 176 throws SAXException 177 { 178 handleMixedContent(); 179 180 BeanPropertyDescriptor propDesc = null; 181 FieldDesc fieldDesc = null; 182 183 SOAPConstants soapConstants = context.getSOAPConstants(); 184 String encodingStyle = context.getEncodingStyle(); 185 boolean isEncoded = Constants.isSOAP_ENC(encodingStyle); 186 187 QName elemQName = new QName(namespace, localName); 188 // The collectionIndex needs to be reset for Beans with multiple arrays 189 if ((prevQName == null) || (!prevQName.equals(elemQName))) { 190 collectionIndex = -1; 191 } 192 193 boolean isArray = false; 194 QName itemQName = null; 195 if (typeDesc != null) { 196 // Lookup the name appropriately (assuming an unqualified 197 // name for SOAP encoding, using the namespace otherwise) 198 String fieldName = typeDesc.getFieldNameForElement(elemQName, 199 isEncoded); 200 propDesc = (BeanPropertyDescriptor)propertyMap.get(fieldName); 201 fieldDesc = typeDesc.getFieldByName(fieldName); 202 203 if (fieldDesc != null) { 204 ElementDesc element = (ElementDesc)fieldDesc; 205 isArray = element.isMaxOccursUnbounded(); 206 itemQName = element.getItemQName(); 207 } 208 } 209 210 if (propDesc == null) { 211 // look for a field by this name. 212 propDesc = (BeanPropertyDescriptor) propertyMap.get(localName); 213 } 214 215 // Workaround 216 if (propDesc == null) { 217 StringBuffer sb=new StringBuffer(); 218 sb.append(Character.toLowerCase(localName.charAt(0))); 219 if(localName.length()>1)sb.append(localName.substring(1)); 220 // look for a field by this name. 221 propDesc = (BeanPropertyDescriptor) propertyMap.get(sb.toString()); 222 223 } 224 225 // try and see if this is an xsd:any namespace="##any" element before 226 // reporting a problem 227 if (propDesc == null || 228 (((prevQName != null) && prevQName.equals(elemQName) && 229 !(propDesc.isIndexed()||isArray) 230 && getAnyPropertyDesc() != null ))) { 231 // try to put unknown elements into a SOAPElement property, if 232 // appropriate 233 prevQName = elemQName; 234 propDesc = getAnyPropertyDesc(); 235 if (propDesc != null) { 236 try { 237 MessageElement [] curElements = (MessageElement[])propDesc.get(value); 238 int length = 0; 239 if (curElements != null) { 240 length = curElements.length; 241 } 242 MessageElement [] newElements = new MessageElement[length + 1]; 243 if (curElements != null) { 244 System.arraycopy(curElements, 0, 245 newElements, 0, length); 246 } 247 MessageElement thisEl = context.getCurElement(); 248 249 newElements[length] = thisEl; 250 propDesc.set(value, newElements); 251 // if this is the first pass through the MessageContexts 252 // make sure that the correct any element is set, 253 // that is the child of the current MessageElement, however 254 // on the first pass this child has not been set yet, so 255 // defer it to the child SOAPHandler 256 if (!localName.equals(thisEl.getName())) { 257 return new SOAPHandler(newElements, length); 258 } 259 return new SOAPHandler(); 260 } catch (Exception e) { 261 throw new SAXException(e); 262 } 263 } 264 } 265 266 267 if (propDesc == null) { 268 // No such field 269 throw new SAXException( 270 Messages.getMessage("badElem00", javaType.getName(), 271 localName)); 272 } 273 274 prevQName = elemQName; 275 // Get the child's xsi:type if available 276 QName childXMLType = context.getTypeFromAttributes(namespace, 277 localName, 278 attributes); 279 String href = attributes.getValue(soapConstants.getAttrHref()); 280 Class fieldType = propDesc.getType(); 281 282 // If no xsi:type or href, check the meta-data for the field 283 if (childXMLType == null && fieldDesc != null && href == null) { 284 childXMLType = fieldDesc.getXmlType(); 285 if (itemQName != null) { 286 // This is actually a wrapped literal array and should be 287 // deserialized with the ArrayDeserializer 288 childXMLType = Constants.SOAP_ARRAY; 289 fieldType = propDesc.getActualType(); 290 } else { 291 childXMLType = fieldDesc.getXmlType(); 292 } 293 } 294 295 // Get Deserializer for child, default to using DeserializerImpl 296 Deserializer dSer = getDeserializer(childXMLType, 297 fieldType, 298 href, 299 context); 300 301 // It is an error if the dSer is not found - the only case where we 302 // wouldn't have a deserializer at this point is when we're trying 303 // to deserialize something we have no clue about (no good xsi:type, 304 // no good metadata). 305 if (dSer == null) { 306 dSer = context.getDeserializerForClass(propDesc.getType()); 307 } 308 309 // Fastpath nil checks... 310 if (context.isNil(attributes)) { 311 if ((propDesc.isIndexed()||isArray)) { 312 if (!((dSer != null) && (dSer instanceof ArrayDeserializer))) { 313 collectionIndex++; 314 dSer.registerValueTarget(new BeanPropertyTarget(value, 315 propDesc, collectionIndex)); 316 addChildDeserializer(dSer); 317 return (SOAPHandler)dSer; 318 } 319 } 320 return null; 321 } 322 323 if (dSer == null) { 324 throw new SAXException(Messages.getMessage("noDeser00", 325 childXMLType.toString())); 326 } 327 328 if (constructorToUse != null) { 329 if (constructorTarget == null) { 330 constructorTarget = new ConstructorTarget(constructorToUse, this); 331 } 332 dSer.registerValueTarget(constructorTarget); 333 } else if (propDesc.isWriteable()) { 334 // If this is an indexed property, and the deserializer we found 335 // was NOT the ArrayDeserializer, this is a non-SOAP array: 336 // <bean> 337 // <field>value1</field> 338 // <field>value2</field> 339 // ... 340 // In this case, we want to use the collectionIndex and make sure 341 // the deserialized value for the child element goes into the 342 // right place in the collection. 343 344 // Register value target 345 if ((itemQName != null || propDesc.isIndexed() || isArray) && !(dSer instanceof ArrayDeserializer)) { 346 collectionIndex++; 347 dSer.registerValueTarget(new BeanPropertyTarget(value, 348 propDesc, collectionIndex)); 349 } else { 350 // If we're here, the element maps to a single field value, 351 // whether that be a "basic" type or an array, so use the 352 // normal (non-indexed) BeanPropertyTarget form. 353 collectionIndex = -1; 354 dSer.registerValueTarget(new BeanPropertyTarget(value, 355 propDesc)); 356 } 357 } 358 359 // Let the framework know that we need this deserializer to complete 360 // for the bean to complete. 361 addChildDeserializer(dSer); 362 363 return (SOAPHandler)dSer; 364 } 365 366 /** 367 * Get a BeanPropertyDescriptor which indicates where we should 368 * put extensibility elements (i.e. XML which falls under the 369 * auspices of an <xsd:any> declaration in the schema) 370 * 371 * @return an appropriate BeanPropertyDescriptor, or null 372 */ 373 public BeanPropertyDescriptor getAnyPropertyDesc() { 374 if (typeDesc == null) 375 return null; 376 377 return typeDesc.getAnyDesc(); 378 } 379 380 /** 381 * Set the bean properties that correspond to element attributes. 382 * 383 * This method is invoked after startElement when the element requires 384 * deserialization (i.e. the element is not an href and the value is not 385 * nil.) 386 * @param namespace is the namespace of the element 387 * @param localName is the name of the element 388 * @param prefix is the prefix of the element 389 * @param attributes are the attributes on the element...used to get the 390 * type 391 * @param context is the DeserializationContext 392 */ 393 public void onStartElement(String namespace, String localName, 394 String prefix, Attributes attributes, 395 DeserializationContext context) 396 throws SAXException { 397 398 // The value should have been created or assigned already. 399 // This code may no longer be needed. 400 if (value == null && constructorToUse == null) { 401 // create a value 402 try { 403 value=javaType.newInstance(); 404 } catch (Exception e) { 405 throw new SAXException(Messages.getMessage("cantCreateBean00", 406 javaType.getName(), 407 e.toString())); 408 } 409 } 410 411 // If no type description meta data, there are no attributes, 412 // so we are done. 413 if (typeDesc == null) 414 return; 415 416 // loop through the attributes and set bean properties that 417 // correspond to attributes 418 for (int i=0; i < attributes.getLength(); i++) { 419 QName attrQName = new QName(attributes.getURI(i), 420 attributes.getLocalName(i)); 421 String fieldName = typeDesc.getFieldNameForAttribute(attrQName); 422 if (fieldName == null) 423 continue; 424 425 FieldDesc fieldDesc = typeDesc.getFieldByName(fieldName); 426 427 // look for the attribute property 428 BeanPropertyDescriptor bpd = 429 (BeanPropertyDescriptor) propertyMap.get(fieldName); 430 if (bpd != null) { 431 if (constructorToUse == null) { 432 // check only if default constructor 433 if (!bpd.isWriteable() || bpd.isIndexed()) continue ; 434 } 435 436 // Get the Deserializer for the attribute 437 Deserializer dSer = getDeserializer(fieldDesc.getXmlType(), 438 bpd.getType(), 439 null, 440 context); 441 if (dSer == null) { 442 dSer = context.getDeserializerForClass(bpd.getType()); 443 444 // The java type is an array, but the context didn't 445 // know that we are an attribute. Better stick with 446 // simple types.. 447 if (dSer instanceof ArrayDeserializer) 448 { 449 SimpleListDeserializerFactory factory = 450 new SimpleListDeserializerFactory(bpd.getType(), 451 fieldDesc.getXmlType()); 452 dSer = (Deserializer) 453 factory.getDeserializerAs(dSer.getMechanismType()); 454 } 455 } 456 457 if (dSer == null) 458 throw new SAXException( 459 Messages.getMessage("unregistered00", 460 bpd.getType().toString())); 461 462 if (! (dSer instanceof SimpleDeserializer)) 463 throw new SAXException( 464 Messages.getMessage("AttrNotSimpleType00", 465 bpd.getName(), 466 bpd.getType().toString())); 467 468 // Success! Create an object from the string and set 469 // it in the bean 470 try { 471 dSer.onStartElement(namespace, localName, prefix, 472 attributes, context); 473 Object val = ((SimpleDeserializer)dSer). 474 makeValue(attributes.getValue(i)); 475 if (constructorToUse == null) { 476 bpd.set(value, val); 477 } else { 478 // add value for our constructor 479 if (constructorTarget == null) { 480 constructorTarget = new ConstructorTarget(constructorToUse, this); 481 } 482 constructorTarget.set(val); 483 } 484 } catch (Exception e) { 485 throw new SAXException(e); 486 } 487 488 } // if 489 } // attribute loop 490 } 491 492 /** 493 * Get the Deserializer for the attribute or child element. 494 * @param xmlType QName of the attribute/child element or null if not known. 495 * @param javaType Class of the corresponding property 496 * @param href String is the value of the href attribute, which is used 497 * to determine whether the child element is complete or an 498 * href to another element. 499 * @param context DeserializationContext 500 * @return Deserializer or null if not found. 501 */ 502 protected Deserializer getDeserializer(QName xmlType, 503 Class javaType, 504 String href, 505 DeserializationContext context) { 506 if (javaType.isArray()) { 507 context.setDestinationClass(javaType); 508 } 509 // See if we have a cached deserializer 510 if (cacheStringDSer != null) { 511 if (String.class.equals(javaType) && 512 href == null && 513 (cacheXMLType == null && xmlType == null || 514 cacheXMLType != null && cacheXMLType.equals(xmlType))) { 515 cacheStringDSer.reset(); 516 return cacheStringDSer; 517 } 518 } 519 520 Deserializer dSer = null; 521 522 if (xmlType != null && href == null) { 523 // Use the xmlType to get the deserializer. 524 dSer = context.getDeserializerForType(xmlType); 525 } else { 526 // If the xmlType is not set, get a default xmlType 527 TypeMapping tm = context.getTypeMapping(); 528 QName defaultXMLType = tm.getTypeQName(javaType); 529 // If there is not href, then get the deserializer 530 // using the javaType and default XMLType, 531 // If there is an href, the create the generic 532 // DeserializerImpl and set its default type (the 533 // default type is used if the href'd element does 534 // not have an xsi:type. 535 if (href == null) { 536 dSer = context.getDeserializer(javaType, defaultXMLType); 537 } else { 538 dSer = new DeserializerImpl(); 539 context.setDestinationClass(javaType); 540 dSer.setDefaultType(defaultXMLType); 541 } 542 } 543 if (javaType.equals(String.class) && 544 dSer instanceof SimpleDeserializer) { 545 cacheStringDSer = (SimpleDeserializer) dSer; 546 cacheXMLType = xmlType; 547 } 548 return dSer; 549 } 550 551 public void characters(char[] chars, int start, int end) throws SAXException { 552 val.write(chars, start, end); 553 } 554 555 public void onEndElement(String namespace, String localName, 556 DeserializationContext context) throws SAXException { 557 handleMixedContent(); 558 } 559 560 protected void handleMixedContent() throws SAXException { 561 BeanPropertyDescriptor propDesc = getAnyPropertyDesc(); 562 if (propDesc == null || val.size() == 0) { 563 return; 564 } 565 String textValue = val.toString().trim(); 566 val.reset(); 567 if (textValue.length() == 0) { 568 return; 569 } 570 try { 571 MessageElement[] curElements = (MessageElement[]) propDesc.get(value); 572 int length = 0; 573 if (curElements != null) { 574 length = curElements.length; 575 } 576 MessageElement[] newElements = new MessageElement[length + 1]; 577 if (curElements != null) { 578 System.arraycopy(curElements, 0, 579 newElements, 0, length); 580 } 581 MessageElement thisEl = new MessageElement(new org.apache.axis.message.Text(textValue)); 582 newElements[length] = thisEl; 583 propDesc.set(value, newElements); 584 } catch (Exception e) { 585 throw new SAXException(e); 586 } 587 } 588 }